From 2af9e2f12ff29055a820639575c7c22e06fa6125 Mon Sep 17 00:00:00 2001 From: John Fallows Date: Mon, 12 Feb 2024 19:46:11 -0800 Subject: [PATCH 01/25] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d78206576..801425c161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [0.9.68](https://github.com/aklivity/zilla/tree/0.9.68) (2024-02-13) + +[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.67...0.9.68) + +**Fixed bugs:** + +- Zilla crashes when a large number of MQTT clients connect [\#793](https://github.com/aklivity/zilla/issues/793) + +**Merged pull requests:** + +- Require group host and port for `kafka` coordinator-specific streams [\#794](https://github.com/aklivity/zilla/pull/794) ([jfallows](https://github.com/jfallows)) + ## [0.9.67](https://github.com/aklivity/zilla/tree/0.9.67) (2024-02-11) [Full Changelog](https://github.com/aklivity/zilla/compare/0.9.66...0.9.67) From 8ef7aa4779a68e4f25368e2d798270ad5160d7e6 Mon Sep 17 00:00:00 2001 From: Akram Yakubov Date: Wed, 14 Feb 2024 12:52:52 -0800 Subject: [PATCH 02/25] Zilla is validating env vars before replacing them (#797) --- .../engine/config/EngineConfigAnnotator.java | 160 ++++++++++++++++++ .../engine/config/EngineConfigReader.java | 90 +--------- .../config/EngineConfigAnnotatorTest.java | 60 +++++++ .../schema/prometheus.schema.patch.json | 1 + 4 files changed, 224 insertions(+), 87 deletions(-) create mode 100644 runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java create mode 100644 runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java new file mode 100644 index 0000000000..670b8e29de --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java @@ -0,0 +1,160 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.config; + +import java.util.LinkedList; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonValue; + +import org.agrona.collections.MutableInteger; + +public final class EngineConfigAnnotator +{ + private final LinkedList schemaKeys; + private final LinkedList schemaIndexes; + + public EngineConfigAnnotator() + { + this.schemaKeys = new LinkedList<>(); + this.schemaIndexes = new LinkedList<>(); + } + + public JsonObject annotate( + JsonObject jsonObject) + { + schemaKeys.clear(); + schemaIndexes.clear(); + + return (JsonObject) annotateJsonObject(jsonObject); + } + + private JsonValue annotateJsonObject( + JsonObject jsonObject) + { + JsonObjectBuilder builder = Json.createObjectBuilder(); + + jsonObject.forEach((key, value) -> + { + schemaKeys.push(key); + + parse: + if ("expression".equals(key)) + { + builder.add(key, value); + } + else if (value.getValueType() == JsonValue.ValueType.OBJECT) + { + builder.add(key, annotateJsonObject(value.asJsonObject())); + } + else if (value.getValueType() == JsonValue.ValueType.ARRAY) + { + if (jsonObject.containsKey("type") && key.equals("enum")) + { + break parse; + } + builder.add(key, annotateJsonArray(value.asJsonArray())); + } + else if (key.equals("type") && isPrimitiveType(value)) + { + builder.add("anyOf", createAnyOfTypes(jsonObject)); + } + else if (jsonObject.containsKey("type") && + isPrimitiveType(jsonObject.get("type"))) + { + if (key.equals("title") || key.equals("description")) + { + builder.add(key, value); + } + } + else + { + builder.add(key, value); + } + + schemaKeys.pop(); + }); + + return builder.build(); + } + + private JsonValue annotateJsonArray( + JsonArray jsonArray) + { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + + MutableInteger index = new MutableInteger(); + + jsonArray.forEach(item -> + { + schemaIndexes.push(index.value++); + if (item.getValueType() == JsonValue.ValueType.OBJECT) + { + arrayBuilder.add(annotateJsonObject(item.asJsonObject())); + } + else + { + arrayBuilder.add(item); + } + schemaIndexes.pop(); + }); + + return arrayBuilder.build(); + } + + private boolean isPrimitiveType( + JsonValue type) + { + String typeText = type.toString().replaceAll("\"", ""); + return "string".equals(typeText) || + "integer".equals(typeText) || + "boolean".equals(typeText) || + "number".equals(typeText); + } + + private JsonArray createAnyOfTypes( + JsonObject properties) + { + JsonArrayBuilder anyOfArrayBuilder = Json.createArrayBuilder(); + JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); + + properties.forEach((key, value) -> + { + if (!"title".equals(key) && !"description".equals(key)) + { + objectBuilder.add(key, value); + } + }); + + anyOfArrayBuilder.add(objectBuilder); + + if (!(!schemaKeys.isEmpty() && + schemaKeys.size() > 1 && + "oneOf".equals(schemaKeys.get(1))) || + schemaIndexes.peek() == 0) + { + anyOfArrayBuilder.add(Json.createObjectBuilder() + .add("$ref", "#/$defs/expression") + ); + } + + return anyOfArrayBuilder.build(); + } +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java index 3c047b6435..a70c9c7bbe 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java @@ -30,14 +30,10 @@ import java.util.List; import java.util.function.Consumer; -import jakarta.json.Json; import jakarta.json.JsonArray; -import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonPatch; import jakarta.json.JsonReader; -import jakarta.json.JsonValue; import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; @@ -62,6 +58,7 @@ public final class EngineConfigReader private final Collection schemaTypes; private final Consumer logger; + public EngineConfigReader( ConfigAdapterContext context, Resolver expressions, @@ -190,7 +187,8 @@ private boolean validateAnnotatedSchema( validate: try { - final JsonObject annotatedSchemaObject = (JsonObject) annotateJsonObject(schemaObject); + final EngineConfigAnnotator annotator = new EngineConfigAnnotator(); + final JsonObject annotatedSchemaObject = annotator.annotate(schemaObject); if (logger != null) { @@ -249,87 +247,5 @@ private boolean validateAnnotatedSchema( } - private JsonValue annotateJsonObject( - JsonObject jsonObject) - { - JsonObjectBuilder builder = Json.createObjectBuilder(); - - jsonObject.forEach((key, value) -> - { - if ("expression".equals(key)) - { - builder.add(key, value); - } - else if (value.getValueType() == JsonValue.ValueType.OBJECT) - { - builder.add(key, annotateJsonObject(value.asJsonObject())); - } - else if (value.getValueType() == JsonValue.ValueType.ARRAY) - { - builder.add(key, annotateJsonArray(value.asJsonArray())); - } - else if (key.equals("type") && - isPrimitiveType(value.toString().replaceAll("\"", ""))) - { - JsonValue pattern = jsonObject.get("pattern"); - builder.add(key, value); - builder.add("anyOf", createOneOfTypes(value.toString().replaceAll("\"", ""), pattern)); - } - else if (!"pattern".equals(key)) - { - builder.add(key, value); - } - }); - - return builder.build(); - } - - private JsonValue annotateJsonArray( - JsonArray jsonArray) - { - JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); - jsonArray.forEach(item -> - { - if (item.getValueType() == JsonValue.ValueType.OBJECT) - { - arrayBuilder.add(annotateJsonObject(item.asJsonObject())); - } - else - { - arrayBuilder.add(item); - } - }); - - return arrayBuilder.build(); - } - - private boolean isPrimitiveType( - String type) - { - return "string".equals(type) || - "integer".equals(type) || - "boolean".equals(type) || - "number".equals(type); - } - - private JsonArray createOneOfTypes( - String originalType, - JsonValue pattern) - { - JsonArrayBuilder oneOfArrayBuilder = Json.createArrayBuilder(); - JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); - objectBuilder.add("type", originalType); - if (pattern != null) - { - objectBuilder.add("pattern", pattern); - } - oneOfArrayBuilder.add(objectBuilder); - - oneOfArrayBuilder.add(Json.createObjectBuilder() - .add("$ref", "#/$defs/expression") - ); - - return oneOfArrayBuilder.build(); - } } diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java new file mode 100644 index 0000000000..2b3d2fa42b --- /dev/null +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.config; + +import static org.junit.Assert.assertTrue; + +import java.io.StringReader; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; + +import org.junit.Test; + +public class EngineConfigAnnotatorTest +{ + @Test + public void shouldAnnotateJsonSchema() + { + final String json = new String("{" + + " \"title\": \"Port\"," + + " \"oneOf\":" + + " [" + + " {" + + " \"type\": \"integer\"" + + " }," + + " {" + + " \"type\": \"string\"," + + " \"pattern\": \"^\\\\d+(-\\\\d+)?$\"" + + " }" + + " ]" + + "}"); + try (JsonReader jsonReader = Json.createReader(new StringReader(json))) + { + JsonObject jsonObject = jsonReader.readObject(); + EngineConfigAnnotator annotator = new EngineConfigAnnotator(); + final JsonObject annotated = annotator.annotate(jsonObject); + + JsonArray jsonArray = annotator.annotate(annotated).getJsonArray("oneOf"); + + assertTrue(jsonArray.get(0).asJsonObject().asJsonObject().getJsonArray("anyOf").size() == 2); + assertTrue(jsonArray.get(1).asJsonObject().asJsonObject().getJsonArray("anyOf").size() == 1); + + } + } +} diff --git a/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json b/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json index 4eee03c61a..336ed01046 100644 --- a/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json +++ b/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json @@ -41,6 +41,7 @@ "scheme": { "title": "Scheme", + "type": "string", "enum": [ "http" From 985fad2b6eab23733b200fdfc4ad7532d5eeac5c Mon Sep 17 00:00:00 2001 From: Akram Yakubov Date: Wed, 14 Feb 2024 16:26:30 -0800 Subject: [PATCH 03/25] Fix kafka sasl schema validation to support expressions (#798) --- .../kafka/schema/kafka.schema.patch.json | 111 ++---------------- 1 file changed, 12 insertions(+), 99 deletions(-) diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json index 9e561112d5..53f7883936 100644 --- a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json @@ -112,110 +112,23 @@ "title": "Mechanism", "type": "string", "enum": [ "plain", "scram-sha-1", "scram-sha-256", "scram-sha-512" ] - } - }, - "oneOf": - [ - { - "additionalProperties": false, - "properties": - { - "mechanism": - { - "const": "plain" - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "username", - "password" - ] - }, - { - "additionalProperties": false, - "properties": - { - "mechanism": - { - "const": "scram-sha-1" - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "username", - "password" - ] }, + "username": { - "additionalProperties": false, - "properties": - { - "mechanism": - { - "const": "scram-sha-256" - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "username", - "password" - ] + "title": "Username", + "type": "string" }, + "password": { - "additionalProperties": false, - "properties": - { - "mechanism": - { - "const": "scram-sha-512" - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "username", - "password" - ] + "title": "Password", + "type": "string" } + }, + "required": + [ + "mechanism", + "username", + "password" ] } }, From e72f0a1e3b93ccffb9403811c3c2cb214030aaa9 Mon Sep 17 00:00:00 2001 From: Akram Yakubov Date: Fri, 16 Feb 2024 06:58:54 -0800 Subject: [PATCH 04/25] Support k3po ephemeral option (#801) --- .../k3po/ext/behavior/ZillaChannelAddress.java | 14 ++++++++++---- .../ext/behavior/ZillaChannelAddressFactory.java | 6 ++++-- .../internal/k3po/ext/types/ZillaTypeSystem.java | 2 ++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddress.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddress.java index d31970af0c..cb53b75432 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddress.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddress.java @@ -28,38 +28,44 @@ public final class ZillaChannelAddress extends ChannelAddress private final long authorization; private final String namespace; private final String binding; + private final String ephemeralName; public ZillaChannelAddress( URI location, long authorization, - String namespace) + String namespace, + String ephemeral) { - this(location, authorization, namespace, bindingName(location)); + this(location, authorization, namespace, bindingName(location), ephemeral); } private ZillaChannelAddress( URI location, long authorization, String namespace, - String binding) + String binding, + String ephemeral) { super(location); this.authorization = authorization; this.namespace = requireNonNull(namespace); this.binding = requireNonNull(binding); + this.ephemeralName = requireNonNull(ephemeral); } private ZillaChannelAddress( URI location, ChannelAddress transport, boolean ephemeral, + String ephemeralName, long authorization, String namespace, String binding) { super(location, transport, ephemeral); + this.ephemeralName = ephemeralName; this.authorization = authorization; this.namespace = requireNonNull(namespace); this.binding = requireNonNull(binding); @@ -97,7 +103,7 @@ private ZillaChannelAddress newEphemeralAddress( URI location, ChannelAddress transport) { - return new ZillaChannelAddress(location, transport, true, authorization, "ephemeral", binding); + return new ZillaChannelAddress(location, transport, true, ephemeralName, authorization, ephemeralName, binding); } private static String bindingName( diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddressFactory.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddressFactory.java index a800d682dd..04ad0df527 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddressFactory.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddressFactory.java @@ -18,6 +18,7 @@ import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_AUTHORIZATION; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_BUDGET_ID; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_BYTE_ORDER; +import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_EPHEMERAL; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_PADDING; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_REPLY_TO; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_STREAM_ID; @@ -72,7 +73,7 @@ protected ChannelAddress newChannelAddress0( } } - Collection> allOptionTypes = asList(OPTION_REPLY_TO, OPTION_WINDOW, OPTION_BUDGET_ID, + Collection> allOptionTypes = asList(OPTION_EPHEMERAL, OPTION_REPLY_TO, OPTION_WINDOW, OPTION_BUDGET_ID, OPTION_STREAM_ID, OPTION_PADDING, OPTION_UPDATE, OPTION_AUTHORIZATION, OPTION_THROTTLE, OPTION_TRANSMISSION, OPTION_BYTE_ORDER); for (TypeInfo optionType : allOptionTypes) @@ -90,7 +91,8 @@ protected ChannelAddress newChannelAddress0( final long authorization = (Long) options.getOrDefault(OPTION_AUTHORIZATION.getName(), 0L); final String replyTo = (String) options.getOrDefault(OPTION_REPLY_TO.getName(), "test"); + final String ephemeral = (String) options.getOrDefault(OPTION_EPHEMERAL.getName(), "ephemeral"); - return new ZillaChannelAddress(location, authorization, replyTo); + return new ZillaChannelAddress(location, authorization, replyTo, ephemeral); } } diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/types/ZillaTypeSystem.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/types/ZillaTypeSystem.java index f7222b0834..d404938aef 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/types/ZillaTypeSystem.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/types/ZillaTypeSystem.java @@ -30,6 +30,7 @@ public final class ZillaTypeSystem implements TypeSystemSpi { public static final String NAME = "zilla"; + public static final TypeInfo OPTION_EPHEMERAL = new TypeInfo<>("ephemeral", String.class); public static final TypeInfo OPTION_REPLY_TO = new TypeInfo<>("replyTo", String.class); public static final TypeInfo OPTION_WINDOW = new TypeInfo<>("window", Integer.class); public static final TypeInfo OPTION_SHARED_WINDOW = new TypeInfo<>("sharedWindow", Integer.class); @@ -95,6 +96,7 @@ public ZillaTypeSystem() this.acceptOptions = unmodifiableSet(acceptOptions); Set> connectOptions = new LinkedHashSet<>(); + connectOptions.add(OPTION_EPHEMERAL); connectOptions.add(OPTION_REPLY_TO); connectOptions.add(OPTION_WINDOW); connectOptions.add(OPTION_SHARED_WINDOW); From 1f5dba5e3d2f8184ba756a05fe134347ce8fa4ae Mon Sep 17 00:00:00 2001 From: John Fallows Date: Tue, 20 Feb 2024 20:32:05 -0800 Subject: [PATCH 05/25] Include config exception cause (#805) --- .../zilla/runtime/engine/config/ConfigException.java | 7 +++++++ .../runtime/engine/internal/registry/EngineManager.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/ConfigException.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/ConfigException.java index c22bed5226..d909b507d9 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/ConfigException.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/ConfigException.java @@ -24,4 +24,11 @@ public ConfigException( { super(message); } + + public ConfigException( + String message, + Throwable cause) + { + super(message, cause); + } } diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineManager.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineManager.java index 44918f788d..b276936737 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineManager.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineManager.java @@ -153,7 +153,7 @@ public EngineConfig reconfigure( if (current == null) { - throw new ConfigException("Engine configuration failed"); + throw new ConfigException("Engine configuration failed", ex); } } From dace1d7f0efb9cd026b90f57a72320200ad565ed Mon Sep 17 00:00:00 2001 From: John Fallows Date: Tue, 20 Feb 2024 21:49:33 -0800 Subject: [PATCH 06/25] Include qualified vault name on binding (#806) --- .../io/aklivity/zilla/runtime/engine/Engine.java | 16 ++++++++++++++-- .../runtime/engine/config/BindingConfig.java | 1 + .../engine/internal/registry/EngineManager.java | 12 ++++++++++++ .../runtime/engine/namespace/NamespacedId.java | 8 ++++---- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java index 7a8f6935a8..aa9f6edb4d 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java @@ -187,8 +187,20 @@ public final class Engine implements Collector, AutoCloseable final Map guardsByType = guards.stream() .collect(Collectors.toMap(g -> g.name(), g -> g)); - EngineManager manager = new EngineManager(schemaTypes, bindingsByType::get, guardsByType::get, - labels::supplyLabelId, maxWorkers, tuning, workers, logger, context, config, extensions, this::readURL); + EngineManager manager = new EngineManager( + schemaTypes, + bindingsByType::get, + guardsByType::get, + labels::supplyLabelId, + labels::lookupLabel, + maxWorkers, + tuning, + workers, + logger, + context, + config, + extensions, + this::readURL); this.configURL = config.configURL(); String protocol = configURL.getProtocol(); diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/BindingConfig.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/BindingConfig.java index b9e2132e18..0011a4a587 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/BindingConfig.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/BindingConfig.java @@ -29,6 +29,7 @@ public class BindingConfig public transient ToLongFunction resolveId; public transient long vaultId; + public transient String qvault; public transient long[] metricIds; diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineManager.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineManager.java index b276936737..e7adb3b6eb 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineManager.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineManager.java @@ -71,6 +71,7 @@ public class EngineManager private final Function bindingByType; private final Function guardByType; private final ToIntFunction supplyId; + private final IntFunction supplyName; private final IntFunction> maxWorkers; private final Tuning tuning; private final Collection dispatchers; @@ -89,6 +90,7 @@ public EngineManager( Function bindingByType, Function guardByType, ToIntFunction supplyId, + IntFunction supplyName, IntFunction> maxWorkers, Tuning tuning, Collection dispatchers, @@ -102,6 +104,7 @@ public EngineManager( this.bindingByType = bindingByType; this.guardByType = guardByType; this.supplyId = supplyId; + this.supplyName = supplyName; this.maxWorkers = maxWorkers; this.tuning = tuning; this.dispatchers = dispatchers; @@ -242,6 +245,7 @@ private void process( if (binding.vault != null) { binding.vaultId = resolver.resolve(binding.vault); + binding.qvault = resolver.format(binding.vaultId); } if (binding.catalogs != null) @@ -439,6 +443,14 @@ private long resolve( return id; } + + private String format( + long namespacedId) + { + return String.format("%s:%s", + supplyName.apply(NamespacedId.namespaceId(namespacedId)), + supplyName.apply(NamespacedId.localId(namespacedId))); + } } private static final class NamespaceConfigAdapterContext implements ConfigAdapterContext diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/namespace/NamespacedId.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/namespace/NamespacedId.java index 7d6b742873..9a4be5e39e 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/namespace/NamespacedId.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/namespace/NamespacedId.java @@ -18,15 +18,15 @@ public final class NamespacedId { public static int namespaceId( - long bindingId) + long namespacedId) { - return (int)(bindingId >> Integer.SIZE) & 0xffff_ffff; + return (int)(namespacedId >> Integer.SIZE) & 0xffff_ffff; } public static int localId( - long bindingId) + long namespacedId) { - return (int)(bindingId >> 0) & 0xffff_ffff; + return (int)(namespacedId >> 0) & 0xffff_ffff; } public static long id( From 497197ab145f952fd7888ad9a0360e61652d22a6 Mon Sep 17 00:00:00 2001 From: bmaidics Date: Wed, 21 Feb 2024 22:07:33 +0100 Subject: [PATCH 07/25] Support asyncapi mqtt proxy using asyncapi.yaml (#764) --- cloud/docker-image/pom.xml | 6 + .../src/main/docker/zpm.json.template | 1 + incubator/binding-asyncapi.spec/COPYRIGHT | 13 + incubator/binding-asyncapi.spec/LICENSE | 201 + incubator/binding-asyncapi.spec/NOTICE | 24 + .../binding-asyncapi.spec/NOTICE.template | 18 + incubator/binding-asyncapi.spec/mvnw | 310 + incubator/binding-asyncapi.spec/mvnw.cmd | 182 + incubator/binding-asyncapi.spec/pom.xml | 166 + .../binding/asyncapi/AsyncapiFunctions.java | 199 + .../specs/binding/asyncapi/AsyncapiSpecs.java | 23 + .../src/main/moditect/module-info.java | 19 + ...kaazing.k3po.lang.el.spi.FunctionMapperSpi | 1 + .../resources/META-INF/zilla/asyncapi.idl | 27 + .../asyncapi/config/client.secure.yaml | 41 + .../specs/binding/asyncapi/config/client.yaml | 29 + .../asyncapi/config/mqtt/asyncapi.yaml | 68 + .../asyncapi/config/server.secure.yaml | 37 + .../specs/binding/asyncapi/config/server.yaml | 26 + .../asyncapi/schema/asyncapi.2.6.schema.json | 3229 ++++++ .../asyncapi/schema/asyncapi.3.0.schema.json | 8746 +++++++++++++++++ .../schema/asyncapi.schema.patch.json | 114 + .../asyncapi/publish.and.subscribe/client.rpt | 92 + .../asyncapi/publish.and.subscribe/server.rpt | 91 + .../mqtt/publish.and.subscribe/client.rpt | 88 + .../mqtt/publish.and.subscribe/server.rpt | 82 + .../asyncapi/AsyncapiFunctionsTest.java | 100 + .../binding/asyncapi/config/SchemaTest.java | 68 + .../asyncapi/streams/asyncapi/AsyncapiIT.java | 48 + .../binding/asyncapi/streams/mqtt/MqttIT.java | 49 + incubator/binding-asyncapi/COPYRIGHT | 12 + incubator/binding-asyncapi/LICENSE | 114 + incubator/binding-asyncapi/NOTICE | 18 + incubator/binding-asyncapi/NOTICE.template | 13 + incubator/binding-asyncapi/mvnw | 310 + incubator/binding-asyncapi/mvnw.cmd | 182 + incubator/binding-asyncapi/pom.xml | 317 + .../asyncapi/config/AsyncapiConfig.java | 31 + .../config/AsyncapiOptionsConfig.java | 50 + .../config/AsyncapiOptionsConfigBuilder.java | 73 + .../asyncapi/internal/AsyncapiBinding.java | 67 + .../internal/AsyncapiBindingAdapter.java | 52 + .../internal/AsyncapiBindingContext.java | 77 + .../internal/AsyncapiBindingFactorySpi.java | 36 + ...AsyncapiClientCompositeBindingAdapter.java | 85 + .../AsyncapiCompositeBindingAdapter.java | 81 + .../internal/AsyncapiConfiguration.java | 41 + ...AsyncapiServerCompositeBindingAdapter.java | 235 + .../config/AsyncapiBindingConfig.java | 91 + .../config/AsyncapiOptionsConfigAdapter.java | 253 + .../internal/config/AsyncapiRouteConfig.java | 40 + .../asyncapi/internal/model/Asyncapi.java | 25 + .../internal/model/AsyncapiBinding.java | 20 + .../internal/model/AsyncapiChannel.java | 29 + .../internal/model/AsyncapiComponents.java | 24 + .../asyncapi/internal/model/AsyncapiItem.java | 21 + .../internal/model/AsyncapiMessage.java | 26 + .../internal/model/AsyncapiOperation.java | 23 + .../internal/model/AsyncapiParameter.java | 20 + .../internal/model/AsyncapiSchema.java | 31 + .../model/AsyncapiSecurityScheme.java | 20 + .../internal/model/AsyncapiServer.java | 24 + .../stream/AsyncapiClientFactory.java | 1067 ++ .../stream/AsyncapiServerFactory.java | 1068 ++ .../internal/stream/AsyncapiState.java | 134 + .../stream/AsyncapiStreamFactory.java | 27 + .../internal/view/AsyncapiChannelView.java | 56 + .../internal/view/AsyncapiMessageView.java | 55 + .../internal/view/AsyncapiResolvable.java | 47 + .../internal/view/AsyncapiSchemaView.java | 81 + .../internal/view/AsyncapiServerView.java | 58 + .../src/main/moditect/module-info.java | 34 + ...a.runtime.engine.binding.BindingFactorySpi | 1 + ...e.engine.config.CompositeBindingAdapterSpi | 1 + ...time.engine.config.OptionsConfigAdapterSpi | 1 + .../AsyncapiBingingFactorySpiTest.java | 96 + .../internal/AsyncapiConfigurationTest.java | 31 + .../AsyncapiOptionsConfigAdapterTest.java | 188 + .../internal/stream/client/AsyncapiIT.java | 66 + .../internal/stream/server/AsyncapiIT.java | 61 + incubator/pom.xml | 7 + .../config/GrpcOptionsConfigAdapterTest.java | 6 +- .../OptionsConfigAdapter.java} | 10 +- .../config/BindingConfigsAdapter.java | 5 +- .../internal/config/CatalogAdapter.java | 5 +- .../internal/config/ExporterAdapter.java | 5 +- .../engine/internal/config/GuardAdapter.java | 5 +- .../engine/internal/config/VaultAdapter.java | 5 +- .../config/OptionsConfigAdapterTest.java | 5 +- 89 files changed, 19642 insertions(+), 22 deletions(-) create mode 100644 incubator/binding-asyncapi.spec/COPYRIGHT create mode 100644 incubator/binding-asyncapi.spec/LICENSE create mode 100644 incubator/binding-asyncapi.spec/NOTICE create mode 100644 incubator/binding-asyncapi.spec/NOTICE.template create mode 100755 incubator/binding-asyncapi.spec/mvnw create mode 100644 incubator/binding-asyncapi.spec/mvnw.cmd create mode 100644 incubator/binding-asyncapi.spec/pom.xml create mode 100644 incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java create mode 100644 incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiSpecs.java create mode 100644 incubator/binding-asyncapi.spec/src/main/moditect/module-info.java create mode 100644 incubator/binding-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi create mode 100644 incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.secure.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.secure.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.2.6.schema.json create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.3.0.schema.json create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java create mode 100644 incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java create mode 100644 incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java create mode 100644 incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/MqttIT.java create mode 100644 incubator/binding-asyncapi/COPYRIGHT create mode 100644 incubator/binding-asyncapi/LICENSE create mode 100644 incubator/binding-asyncapi/NOTICE create mode 100644 incubator/binding-asyncapi/NOTICE.template create mode 100755 incubator/binding-asyncapi/mvnw create mode 100644 incubator/binding-asyncapi/mvnw.cmd create mode 100644 incubator/binding-asyncapi/pom.xml create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBinding.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingFactorySpi.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfiguration.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/Asyncapi.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiBinding.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiChannel.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiComponents.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiItem.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSchema.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSecurityScheme.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiState.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiStreamFactory.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiChannelView.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiResolvable.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java create mode 100644 incubator/binding-asyncapi/src/main/moditect/module-info.java create mode 100644 incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi create mode 100644 incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi create mode 100644 incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi create mode 100644 incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java create mode 100644 incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfigurationTest.java create mode 100644 incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java create mode 100644 incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java create mode 100644 incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java rename runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/{internal/config/OptionsAdapter.java => config/OptionsConfigAdapter.java} (84%) diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index 180f6eb1cd..280ead1a1d 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -43,6 +43,12 @@ ${project.version} runtime + + ${project.groupId} + binding-asyncapi + ${project.version} + runtime + ${project.groupId} binding-echo diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template index 427e9b3f99..4b2d69bc63 100644 --- a/cloud/docker-image/src/main/docker/zpm.json.template +++ b/cloud/docker-image/src/main/docker/zpm.json.template @@ -14,6 +14,7 @@ "dependencies": [ "io.aklivity.zilla:binding-amqp", + "io.aklivity.zilla:binding-asyncapi", "io.aklivity.zilla:binding-echo", "io.aklivity.zilla:binding-fan", "io.aklivity.zilla:binding-filesystem", diff --git a/incubator/binding-asyncapi.spec/COPYRIGHT b/incubator/binding-asyncapi.spec/COPYRIGHT new file mode 100644 index 0000000000..8b1b7215ef --- /dev/null +++ b/incubator/binding-asyncapi.spec/COPYRIGHT @@ -0,0 +1,13 @@ +Copyright ${copyrightYears} Aklivity Inc. + +Aklivity licenses this file to you under the Apache License, +version 2.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. diff --git a/incubator/binding-asyncapi.spec/LICENSE b/incubator/binding-asyncapi.spec/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/incubator/binding-asyncapi.spec/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/incubator/binding-asyncapi.spec/NOTICE b/incubator/binding-asyncapi.spec/NOTICE new file mode 100644 index 0000000000..b7e575e904 --- /dev/null +++ b/incubator/binding-asyncapi.spec/NOTICE @@ -0,0 +1,24 @@ +Licensed under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::binding-mqtt.spec under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-asyncapi.spec/NOTICE.template b/incubator/binding-asyncapi.spec/NOTICE.template new file mode 100644 index 0000000000..e9ed8f0e7b --- /dev/null +++ b/incubator/binding-asyncapi.spec/NOTICE.template @@ -0,0 +1,18 @@ +Licensed under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ \ No newline at end of file diff --git a/incubator/binding-asyncapi.spec/mvnw b/incubator/binding-asyncapi.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-asyncapi.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-asyncapi.spec/mvnw.cmd b/incubator/binding-asyncapi.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-asyncapi.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-asyncapi.spec/pom.xml b/incubator/binding-asyncapi.spec/pom.xml new file mode 100644 index 0000000000..9a561905e4 --- /dev/null +++ b/incubator/binding-asyncapi.spec/pom.xml @@ -0,0 +1,166 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + binding-asyncapi.spec + zilla::incubator::binding-asyncapi.spec + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 11 + 11 + 0.91 + 0 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-mqtt.spec + ${project.version} + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core asyncapi + io.aklivity.zilla.specs.binding.asyncapi.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/binding/asyncapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java new file mode 100644 index 0000000000..b010e97bce --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java @@ -0,0 +1,199 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi; + +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.kaazing.k3po.lang.el.BytesMatcher; +import org.kaazing.k3po.lang.el.Function; +import org.kaazing.k3po.lang.el.spi.FunctionMapperSpi; + +import io.aklivity.zilla.specs.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.specs.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; + +public final class AsyncapiFunctions +{ + @Function + public static AsyncapiBeginExBuilder beginEx() + { + return new AsyncapiBeginExBuilder(); + } + + @Function + public static AsyncapiBeginExMatcherBuilder matchBeginEx() + { + return new AsyncapiBeginExMatcherBuilder(); + } + + public static final class AsyncapiBeginExBuilder + { + private final AsyncapiBeginExFW.Builder beginExRW; + + private AsyncapiBeginExBuilder() + { + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1024 * 8]); + this.beginExRW = new AsyncapiBeginExFW.Builder().wrap(writeBuffer, 0, writeBuffer.capacity()); + } + + public AsyncapiBeginExBuilder typeId( + int typeId) + { + beginExRW.typeId(typeId); + return this; + } + + public AsyncapiBeginExBuilder operationId( + String operationId) + { + beginExRW.operationId(operationId); + return this; + } + + public AsyncapiBeginExBuilder extension( + byte[] extension) + { + beginExRW.extension(e -> e.set(extension)); + return this; + } + + public byte[] build() + { + final AsyncapiBeginExFW beginEx = beginExRW.build(); + final byte[] array = new byte[beginEx.sizeof()]; + beginEx.buffer().getBytes(beginEx.offset(), array); + return array; + } + } + + public static final class AsyncapiBeginExMatcherBuilder + { + private final DirectBuffer bufferRO = new UnsafeBuffer(); + + private final AsyncapiBeginExFW beginExRO = new AsyncapiBeginExFW(); + + private Integer typeId; + private int apiId; + private String operationId; + private OctetsFW.Builder extensionRW; + + public AsyncapiBeginExMatcherBuilder typeId( + int typeId) + { + this.typeId = typeId; + return this; + } + + public AsyncapiBeginExMatcherBuilder operationId( + String operationId) + { + this.operationId = operationId; + return this; + } + + public AsyncapiBeginExMatcherBuilder apiId( + int apiId) + { + this.apiId = apiId; + return this; + } + + public AsyncapiBeginExMatcherBuilder extension( + byte[] extension) + { + assert extensionRW == null; + extensionRW = new OctetsFW.Builder().wrap(new UnsafeBuffer(new byte[1024]), 0, 1024); + + extensionRW.set(Objects.requireNonNullElseGet(extension, () -> new byte[] {})); + + return this; + } + + public BytesMatcher build() + { + return typeId != null ? this::match : buf -> null; + } + + private AsyncapiBeginExFW match( + ByteBuffer byteBuf) throws Exception + { + if (!byteBuf.hasRemaining()) + { + return null; + } + + bufferRO.wrap(byteBuf); + final AsyncapiBeginExFW beginEx = beginExRO.tryWrap(bufferRO, byteBuf.position(), byteBuf.limit()); + + if (beginEx != null && + matchTypeId(beginEx) && + matchApiId(beginEx) && + matchOperationId(beginEx) && + matchExtension(beginEx)) + { + byteBuf.position(byteBuf.position() + beginEx.sizeof()); + return beginEx; + } + throw new Exception(beginEx.toString()); + } + + private boolean matchTypeId( + AsyncapiBeginExFW beginEx) + { + return typeId == beginEx.typeId(); + } + + private boolean matchApiId( + AsyncapiBeginExFW beginEx) + { + return apiId == beginEx.apiId(); + } + + private boolean matchOperationId( + AsyncapiBeginExFW beginEx) + { + return operationId == null || operationId.equals(beginEx.operationId().asString()); + } + + private boolean matchExtension( + final AsyncapiBeginExFW beginEx) + { + return extensionRW == null || extensionRW.build().equals(beginEx.extension()); + } + } + + private AsyncapiFunctions() + { + // utility + } + + public static class Mapper extends FunctionMapperSpi.Reflective + { + public Mapper() + { + super(AsyncapiFunctions.class); + } + + @Override + public String getPrefixName() + { + return "asyncapi"; + } + } +} diff --git a/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiSpecs.java b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiSpecs.java new file mode 100644 index 0000000000..262fc5de9c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiSpecs.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi; + +public final class AsyncapiSpecs +{ + private AsyncapiSpecs() + { + } +} diff --git a/incubator/binding-asyncapi.spec/src/main/moditect/module-info.java b/incubator/binding-asyncapi.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..187284829c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/moditect/module-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +open module io.aklivity.zilla.specs.binding.asyncapi +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/binding-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi new file mode 100644 index 0000000000..194c889324 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi @@ -0,0 +1 @@ +io.aklivity.zilla.specs.binding.asyncapi.AsyncapiFunctions$Mapper diff --git a/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl new file mode 100644 index 0000000000..5741ec1f8f --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope asyncapi +{ + scope stream + { + struct AsyncapiBeginEx extends core::stream::Extension + { + int32 apiId = 0; + string16 operationId = null; + octets extension; + } + } +} diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.secure.yaml new file mode 100644 index 0000000000..47ebc84ec3 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.secure.yaml @@ -0,0 +1,41 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + asyncapi0: + type: asyncapi + kind: client + vault: test0 + options: + specs: + - mqtt/asyncapi.yaml + tcp: + host: localhost + port: + - 7183 + tls: + trust: + - serverca + trustcacerts: true + sni: + - mqtt.example.net + alpn: + - mqtt diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.yaml new file mode 100644 index 0000000000..00b2ea5605 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.yaml @@ -0,0 +1,29 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + asyncapi0: + type: asyncapi + kind: client + options: + specs: + - mqtt/asyncapi.yaml + tcp: + host: localhost + port: + - 7183 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml new file mode 100644 index 0000000000..dd8057f382 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml @@ -0,0 +1,68 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +asyncapi: 3.0.0 +info: + title: Zilla MQTT Proxy + version: 1.0.0 + license: + name: Aklivity Community License +servers: + plain: + host: mqtt://localhost:1883 + protocol: mqtt +defaultContentType: application/json + +channels: + smartylighting: + address: "smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured" + title: MQTT Topic to produce & consume topic. + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + messages: + items: + $ref: '#/components/messages/item' + +operations: + sendEvents: + action: send + channel: + $ref: '#/channels/smartylighting' + + receiveEvents: + action: receive + channel: + $ref: '#/channels/smartylighting' + +components: + parameters: + streetlightId: + description: Street Light ID + location: $message.header#/id + messages: + item: + name: event + title: An event + headers: + type: object + properties: + idempotency-key: + description: Unique identifier for a given event + type: string + id: + description: Street Light ID + type: string diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.secure.yaml new file mode 100644 index 0000000000..25e5d1bcd5 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.secure.yaml @@ -0,0 +1,37 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + composite0: + type: asyncapi + kind: server + vault: test0 + options: + specs: + - mqtt/asyncapi.yaml + tls: + keys: + - localhost + sni: + - mqtt.example.net + alpn: + - mqtt + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.yaml new file mode 100644 index 0000000000..8e65b8c21c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + composite0: + type: asyncapi + kind: server + options: + specs: + - mqtt/asyncapi.yaml + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.2.6.schema.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.2.6.schema.json new file mode 100644 index 0000000000..053cc08f14 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.2.6.schema.json @@ -0,0 +1,3229 @@ +{ + "$id": "http://asyncapi.com/definitions/2.6.0/asyncapi.json", + "$schema": "http://json-schema.org/draft-07/schema", + "title": "AsyncAPI 2.6.0 schema.", + "type": "object", + "required": [ + "asyncapi", + "info", + "channels" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "asyncapi": { + "type": "string", + "enum": [ + "2.6.0" + ], + "description": "The AsyncAPI specification version of this document." + }, + "id": { + "type": "string", + "description": "A unique id representing the application.", + "format": "uri" + }, + "info": { + "$ref": "http://asyncapi.com/definitions/2.6.0/info.json" + }, + "servers": { + "$ref": "http://asyncapi.com/definitions/2.6.0/servers.json" + }, + "defaultContentType": { + "type": "string" + }, + "channels": { + "$ref": "http://asyncapi.com/definitions/2.6.0/channels.json" + }, + "components": { + "$ref": "http://asyncapi.com/definitions/2.6.0/components.json" + }, + "tags": { + "type": "array", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + } + }, + "definitions": { + "http://asyncapi.com/definitions/2.6.0/specificationExtension.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json", + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "http://asyncapi.com/definitions/2.6.0/info.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/info.json", + "type": "object", + "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." + }, + "termsOfService": { + "type": "string", + "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", + "format": "uri" + }, + "contact": { + "$ref": "http://asyncapi.com/definitions/2.6.0/contact.json" + }, + "license": { + "$ref": "http://asyncapi.com/definitions/2.6.0/license.json" + } + }, + "examples": [ + { + "title": "AsyncAPI Sample App", + "description": "This is a sample server.", + "termsOfService": "https://asyncapi.org/terms/", + "contact": { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/contact.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/contact.json", + "type": "object", + "description": "Contact information for the exposed API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/license.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/license.json", + "type": "object", + "required": [ + "name" + ], + "description": "License information for the exposed API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/servers.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/servers.json", + "description": "The Servers Object is a map of Server Objects.", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/server.json" + } + ] + }, + "examples": [ + { + "development": { + "url": "development.gigantic-server.com", + "description": "Development server", + "protocol": "amqp", + "protocolVersion": "0.9.1", + "tags": [ + { + "name": "env:development", + "description": "This environment is meant for developers to run their own tests" + } + ] + }, + "staging": { + "url": "staging.gigantic-server.com", + "description": "Staging server", + "protocol": "amqp", + "protocolVersion": "0.9.1", + "tags": [ + { + "name": "env:staging", + "description": "This environment is a replica of the production environment" + } + ] + }, + "production": { + "url": "api.gigantic-server.com", + "description": "Production server", + "protocol": "amqp", + "protocolVersion": "0.9.1", + "tags": [ + { + "name": "env:production", + "description": "This environment is the live environment available for final users" + } + ] + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/Reference.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/Reference.json", + "type": "object", + "required": [ + "$ref" + ], + "properties": { + "$ref": { + "$ref": "http://asyncapi.com/definitions/2.6.0/ReferenceObject.json" + } + } + }, + "http://asyncapi.com/definitions/2.6.0/ReferenceObject.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/ReferenceObject.json", + "type": "string", + "description": "A simple object to allow referencing other components in the specification, internally and externally.", + "format": "uri-reference", + "examples": [ + { + "$ref": "#/components/schemas/Pet" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/server.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/server.json", + "type": "object", + "description": "An object representing a message broker, a server or any other kind of computer program capable of sending and/or receiving data", + "required": [ + "url", + "protocol" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "url": { + "type": "string", + "description": "A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the AsyncAPI document is being served." + }, + "description": { + "type": "string", + "description": "An optional string describing the host designated by the URL. CommonMark syntax MAY be used for rich text representation." + }, + "protocol": { + "type": "string", + "description": "The protocol this URL supports for connection. Supported protocol include, but are not limited to: amqp, amqps, http, https, ibmmq, jms, kafka, kafka-secure, anypointmq, mqtt, secure-mqtt, solace, stomp, stomps, ws, wss, mercure, googlepubsub." + }, + "protocolVersion": { + "type": "string", + "description": "The version of the protocol used for connection. For instance: AMQP 0.9.1, HTTP 2.0, Kafka 1.0.0, etc." + }, + "variables": { + "description": "A map between a variable name and its value. The value is used for substitution in the server's URL template.", + "$ref": "http://asyncapi.com/definitions/2.6.0/serverVariables.json" + }, + "security": { + "type": "array", + "description": "A declaration of which security mechanisms can be used with this server. The list of values includes alternative security requirement objects that can be used. ", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json" + } + }, + "bindings": { + "description": "A map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the server.", + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of servers.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + } + }, + "examples": [ + { + "url": "development.gigantic-server.com", + "description": "Development server", + "protocol": "kafka", + "protocolVersion": "1.0.0" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/serverVariables.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/serverVariables.json", + "type": "object", + "description": "A map between a variable name and its value. The value is used for substitution in the server's URL template.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/serverVariable.json" + } + ] + } + }, + "http://asyncapi.com/definitions/2.6.0/serverVariable.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/serverVariable.json", + "type": "object", + "description": "An object representing a Server Variable for server URL template substitution.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "enum": { + "type": "array", + "description": "An enumeration of string values to be used if the substitution options are from a limited set.", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "default": { + "type": "string", + "description": "The default value to use for substitution, and to send, if an alternate value is not supplied." + }, + "description": { + "type": "string", + "description": "An optional description for the server variable. " + }, + "examples": { + "type": "array", + "description": "An array of examples of the server variable.", + "items": { + "type": "string" + } + } + } + }, + "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json", + "type": "object", + "description": "Lists of the required security schemes that can be used to execute an operation", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "examples": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/bindingsObject.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for a server.", + "additionalProperties": true, + "properties": { + "http": {}, + "ws": {}, + "amqp": {}, + "amqp1": {}, + "mqtt": {}, + "mqtt5": {}, + "kafka": {}, + "anypointmq": {}, + "nats": {}, + "jms": {}, + "sns": {}, + "sqs": {}, + "stomp": {}, + "redis": {}, + "ibmmq": {}, + "solace": {}, + "googlepubsub": {}, + "pulsar": {} + } + }, + "http://asyncapi.com/definitions/2.6.0/tag.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/tag.json", + "type": "object", + "description": "Allows adding meta data to a single tag.", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the tag." + }, + "description": { + "type": "string", + "description": "A short description for the tag." + }, + "externalDocs": { + "description": "Additional external documentation for this tag.", + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "user", + "description": "User-related messages" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/externalDocs.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/externalDocs.json", + "type": "object", + "additionalProperties": false, + "description": "Allows referencing an external resource for extended documentation.", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string", + "description": "A short description of the target documentation." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URL for the target documentation. This MUST be in the form of an absolute URL." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "examples": [ + { + "description": "Find more info here", + "url": "https://example.com" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/channels.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/channels.json", + "type": "object", + "description": "Holds the relative paths to the individual channel and their operations. Channel paths are relative to servers.", + "propertyNames": { + "type": "string", + "format": "uri-template", + "minLength": 1 + }, + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/channelItem.json" + }, + "examples": [ + { + "user/signedup": { + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/channelItem.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/channelItem.json", + "type": "object", + "description": "Describes the operations available on a single channel.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "$ref": { + "$ref": "http://asyncapi.com/definitions/2.6.0/ReferenceObject.json" + }, + "parameters": { + "$ref": "http://asyncapi.com/definitions/2.6.0/parameters.json" + }, + "description": { + "type": "string", + "description": "A description of the channel." + }, + "servers": { + "type": "array", + "description": "The names of the servers on which this channel is available. If absent or empty then this channel must be available on all servers.", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "publish": { + "$ref": "http://asyncapi.com/definitions/2.6.0/operation.json" + }, + "subscribe": { + "$ref": "http://asyncapi.com/definitions/2.6.0/operation.json" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "examples": [ + { + "description": "This channel is used to exchange messages about users signing up", + "subscribe": { + "summary": "A user signed up.", + "message": { + "description": "A longer description of the message", + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/user" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + } + } + }, + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "exclusive": true + } + } + } + }, + { + "subscribe": { + "message": { + "oneOf": [ + { + "$ref": "#/components/messages/signup" + }, + { + "$ref": "#/components/messages/login" + } + ] + } + } + }, + { + "description": "This application publishes WebUICommand messages to an AMQP queue on RabbitMQ brokers in the Staging and Production environments.", + "servers": [ + "rabbitmqBrokerInProd", + "rabbitmqBrokerInStaging" + ], + "subscribe": { + "message": { + "$ref": "#/components/messages/WebUICommand" + } + }, + "bindings": { + "amqp": { + "is": "queue" + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/parameters.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/parameters.json", + "type": "object", + "description": "JSON objects describing reusable channel parameters.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/parameter.json" + } + ] + }, + "examples": [ + { + "user/{userId}/signup": { + "parameters": { + "userId": { + "description": "Id of the user.", + "schema": { + "type": "string" + } + } + }, + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/parameter.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/parameter.json", + "description": "Describes a parameter included in a channel name.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "schema": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the parameter value", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + } + }, + "examples": [ + { + "user/{userId}/signup": { + "parameters": { + "userId": { + "description": "Id of the user.", + "schema": { + "type": "string" + }, + "location": "$message.payload#/user/id" + } + }, + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/schema.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/schema.json", + "description": "The Schema Object allows the definition of input and output data types. These types can be objects, but also primitives and arrays.", + "allOf": [ + { + "$ref": "http://json-schema.org/draft-07/schema#" + }, + { + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + { + "type": "boolean" + } + ], + "default": {} + }, + "items": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + } + ], + "default": {} + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + }, + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + }, + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + }, + "not": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "default": {} + }, + "propertyNames": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "contains": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "discriminator": { + "type": "string", + "description": "Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. " + }, + "externalDocs": { + "description": "Additional external documentation for this schema.", + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "deprecated": { + "type": "boolean", + "default": false, + "description": "Specifies that a schema is deprecated and SHOULD be transitioned out of usage" + } + } + } + ], + "examples": [ + { + "type": "string", + "format": "email" + }, + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "address": { + "$ref": "#/components/schemas/Address" + }, + "age": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + } + ] + }, + "http://json-schema.org/draft-07/schema": { + "$id": "http://json-schema.org/draft-07/schema", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/nonNegativeInteger" + }, + { + "default": 0 + } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "default": [] + } + }, + "type": [ + "object", + "boolean" + ], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#" + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#" + }, + "maxProperties": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#" + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "propertyNames": { + "format": "regex" + }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#" + }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#" + }, + "then": { + "$ref": "#" + }, + "else": { + "$ref": "#" + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + } + }, + "default": true + }, + "http://asyncapi.com/definitions/2.6.0/operation.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/operation.json", + "type": "object", + "description": "Describes a publish or a subscribe operation. This provides a place to document how and why messages are sent and received.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "traits": { + "type": "array", + "description": "A list of traits to apply to the operation object.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/operationTrait.json" + } + ] + } + }, + "summary": { + "type": "string", + "description": "A short summary of what the operation is about." + }, + "description": { + "type": "string", + "description": "A verbose explanation of the operation." + }, + "security": { + "type": "array", + "description": "A declaration of which security mechanisms are associated with this operation.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json" + } + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of operations.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "operationId": { + "type": "string" + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + }, + "message": { + "$ref": "http://asyncapi.com/definitions/2.6.0/message.json" + } + }, + "examples": [ + { + "user/signedup": { + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/operationTrait.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/operationTrait.json", + "type": "object", + "description": "Describes a trait that MAY be applied to an Operation Object.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "summary": { + "type": "string", + "description": "A short summary of what the operation is about." + }, + "description": { + "type": "string", + "description": "A verbose explanation of the operation." + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of operations.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "operationId": { + "type": "string", + "description": "Unique string used to identify the operation. The id MUST be unique among all operations described in the API." + }, + "security": { + "type": "array", + "description": "A declaration of which security mechanisms are associated with this operation. ", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json" + } + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "examples": [ + { + "bindings": { + "amqp": { + "ack": false + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/message.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/message.json", + "description": "Describes a message received on a given channel and operation.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "oneOf": [ + { + "type": "object", + "required": [ + "oneOf" + ], + "additionalProperties": false, + "properties": { + "oneOf": { + "type": "array", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/message.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "schemaFormat": { + "type": "string" + }, + "contentType": { + "type": "string" + }, + "headers": { + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + { + "properties": { + "type": { + "const": "object" + } + } + } + ] + }, + "messageId": { + "type": "string" + }, + "payload": {}, + "correlationId": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/correlationId.json" + } + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the message." + }, + "name": { + "type": "string", + "description": "Name of the message." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the message." + }, + "description": { + "type": "string", + "description": "A longer description of the message. CommonMark is allowed." + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "description": "List of examples.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "payload" + ] + }, + { + "required": [ + "headers" + ] + } + ], + "properties": { + "name": { + "type": "string", + "description": "Machine readable name of the message example." + }, + "summary": { + "type": "string", + "description": "A brief summary of the message example." + }, + "headers": { + "type": "object", + "description": "Schema definition of the application headers." + }, + "payload": { + "description": "Definition of the message payload. It can be of any type" + } + } + } + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + }, + "traits": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/messageTrait.json" + } + ] + } + } + }, + "allOf": [ + { + "if": { + "not": { + "required": [ + "schemaFormat" + ] + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.aai.asyncapi;version=2.0.0", + "application/vnd.aai.asyncapi+json;version=2.0.0", + "application/vnd.aai.asyncapi+yaml;version=2.0.0", + "application/vnd.aai.asyncapi;version=2.1.0", + "application/vnd.aai.asyncapi+json;version=2.1.0", + "application/vnd.aai.asyncapi+yaml;version=2.1.0", + "application/vnd.aai.asyncapi;version=2.2.0", + "application/vnd.aai.asyncapi+json;version=2.2.0", + "application/vnd.aai.asyncapi+yaml;version=2.2.0", + "application/vnd.aai.asyncapi;version=2.3.0", + "application/vnd.aai.asyncapi+json;version=2.3.0", + "application/vnd.aai.asyncapi+yaml;version=2.3.0", + "application/vnd.aai.asyncapi;version=2.4.0", + "application/vnd.aai.asyncapi+json;version=2.4.0", + "application/vnd.aai.asyncapi+yaml;version=2.4.0", + "application/vnd.aai.asyncapi;version=2.5.0", + "application/vnd.aai.asyncapi+json;version=2.5.0", + "application/vnd.aai.asyncapi+yaml;version=2.5.0", + "application/vnd.aai.asyncapi;version=2.6.0", + "application/vnd.aai.asyncapi+json;version=2.6.0", + "application/vnd.aai.asyncapi+yaml;version=2.6.0" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/schema+json;version=draft-07", + "application/schema+yaml;version=draft-07" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://json-schema.org/draft-07/schema" + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.oai.openapi;version=3.0.0", + "application/vnd.oai.openapi+json;version=3.0.0", + "application/vnd.oai.openapi+yaml;version=3.0.0" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://asyncapi.com/definitions/2.6.0/openapiSchema_3_0.json" + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.apache.avro;version=1.9.0", + "application/vnd.apache.avro+json;version=1.9.0", + "application/vnd.apache.avro+yaml;version=1.9.0" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://asyncapi.com/definitions/2.6.0/avroSchema_v1.json" + } + } + } + } + ] + } + ] + } + ], + "examples": [ + { + "messageId": "userSignup", + "name": "UserSignup", + "title": "User signup", + "summary": "Action to sign a user up.", + "description": "A longer description", + "contentType": "application/json", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "headers": { + "type": "object", + "properties": { + "correlationId": { + "description": "Correlation ID set by application", + "type": "string" + }, + "applicationInstanceId": { + "description": "Unique identifier for a given instance of the publishing application", + "type": "string" + } + } + }, + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/userCreate" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + }, + "correlationId": { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + }, + "traits": [ + { + "$ref": "#/components/messageTraits/commonHeaders" + } + ], + "examples": [ + { + "name": "SimpleSignup", + "summary": "A simple UserSignup example message", + "headers": { + "correlationId": "my-correlation-id", + "applicationInstanceId": "myInstanceId" + }, + "payload": { + "user": { + "someUserKey": "someUserValue" + }, + "signup": { + "someSignupKey": "someSignupValue" + } + } + } + ] + }, + { + "messageId": "userSignup", + "name": "UserSignup", + "title": "User signup", + "summary": "Action to sign a user up.", + "description": "A longer description", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "schemaFormat": "application/vnd.apache.avro+json;version=1.9.0", + "payload": { + "$ref": "path/to/user-create.avsc#/UserCreate" + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/correlationId.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/correlationId.json", + "type": "object", + "description": "An object that specifies an identifier at design time that can used for message tracing and correlation.", + "required": [ + "location" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A optional description of the correlation ID. GitHub Flavored Markdown is allowed." + }, + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the correlation ID", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + } + }, + "examples": [ + { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/messageTrait.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/messageTrait.json", + "type": "object", + "description": "Describes a trait that MAY be applied to a Message Object.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "schemaFormat": { + "type": "string", + "description": "A string containing the name of the schema format/language used to define the message payload." + }, + "contentType": { + "type": "string", + "description": "The content type to use when encoding/decoding a message's payload." + }, + "headers": { + "description": "Schema definition of the application headers.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + { + "properties": { + "type": { + "const": "object" + } + } + } + ] + }, + "messageId": { + "type": "string", + "description": "Unique string used to identify the message. The id MUST be unique among all messages described in the API." + }, + "correlationId": { + "description": "Definition of the correlation ID used for message tracing or matching.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/correlationId.json" + } + ] + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of messages.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the message." + }, + "name": { + "type": "string", + "description": "Name of the message." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the message." + }, + "description": { + "type": "string", + "description": "A longer description of the message. CommonMark is allowed." + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "description": "List of examples.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "payload" + ] + }, + { + "required": [ + "headers" + ] + } + ], + "properties": { + "name": { + "type": "string", + "description": "Machine readable name of the message example." + }, + "summary": { + "type": "string", + "description": "A brief summary of the message example." + }, + "headers": { + "type": "object", + "description": "Schema definition of the application headers." + }, + "payload": { + "description": "Definition of the message payload. It can be of any type" + } + } + } + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "examples": [ + { + "schemaFormat": "application/vnd.apache.avro+json;version=1.9.0", + "contentType": "application/json" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/openapiSchema_3_0.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/openapiSchema_3_0.json", + "type": "object", + "definitions": { + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + } + }, + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": true, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": true, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": true + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/avroSchema_v1.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/avroSchema_v1.json", + "definitions": { + "avroSchema": { + "title": "Avro Schema", + "description": "Root Schema", + "oneOf": [ + { + "$ref": "#/definitions/types" + } + ] + }, + "types": { + "title": "Avro Types", + "description": "Allowed Avro types", + "oneOf": [ + { + "$ref": "#/definitions/primitiveType" + }, + { + "$ref": "#/definitions/primitiveTypeWithMetadata" + }, + { + "$ref": "#/definitions/customTypeReference" + }, + { + "$ref": "#/definitions/avroRecord" + }, + { + "$ref": "#/definitions/avroEnum" + }, + { + "$ref": "#/definitions/avroArray" + }, + { + "$ref": "#/definitions/avroMap" + }, + { + "$ref": "#/definitions/avroFixed" + }, + { + "$ref": "#/definitions/avroUnion" + } + ] + }, + "primitiveType": { + "title": "Primitive Type", + "description": "Basic type primitives.", + "type": "string", + "enum": [ + "null", + "boolean", + "int", + "long", + "float", + "double", + "bytes", + "string" + ] + }, + "primitiveTypeWithMetadata": { + "title": "Primitive Type With Metadata", + "description": "A primitive type with metadata attached.", + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/primitiveType" + } + }, + "required": [ + "type" + ] + }, + "customTypeReference": { + "title": "Custom Type", + "description": "Reference to a ComplexType", + "not": { + "$ref": "#/definitions/primitiveType" + }, + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$" + }, + "avroUnion": { + "title": "Union", + "description": "A Union of types", + "type": "array", + "items": { + "$ref": "#/definitions/avroSchema" + }, + "minItems": 1 + }, + "avroField": { + "title": "Field", + "description": "A field within a Record", + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/name" + }, + "type": { + "$ref": "#/definitions/types" + }, + "doc": { + "type": "string" + }, + "default": true, + "order": { + "enum": [ + "ascending", + "descending", + "ignore" + ] + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + } + }, + "required": [ + "name", + "type" + ] + }, + "avroRecord": { + "title": "Record", + "description": "A Record", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "record" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/avroField" + } + } + }, + "required": [ + "type", + "name", + "fields" + ] + }, + "avroEnum": { + "title": "Enum", + "description": "An enumeration", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "enum" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "symbols": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + } + }, + "required": [ + "type", + "name", + "symbols" + ] + }, + "avroArray": { + "title": "Array", + "description": "An array", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "array" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "items": { + "$ref": "#/definitions/types" + } + }, + "required": [ + "type", + "items" + ] + }, + "avroMap": { + "title": "Map", + "description": "A map of values", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "map" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "values": { + "$ref": "#/definitions/types" + } + }, + "required": [ + "type", + "values" + ] + }, + "avroFixed": { + "title": "Fixed", + "description": "A fixed sized array of bytes", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "fixed" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "size": { + "type": "number" + } + }, + "required": [ + "type", + "name", + "size" + ] + }, + "name": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" + }, + "namespace": { + "type": "string", + "pattern": "^([A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*)*$" + } + }, + "description": "Json-Schema definition for Avro AVSC files.", + "oneOf": [ + { + "$ref": "#/definitions/avroSchema" + } + ], + "title": "Avro Schema Definition" + }, + "http://asyncapi.com/definitions/2.6.0/components.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/components.json", + "type": "object", + "description": "Holds a set of reusable objects for different aspects of the AsyncAPI specification. All objects defined within the components object will have no effect on the API unless they are explicitly referenced from properties outside the components object.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "schemas": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schemas.json" + }, + "servers": { + "$ref": "http://asyncapi.com/definitions/2.6.0/servers.json" + }, + "channels": { + "$ref": "http://asyncapi.com/definitions/2.6.0/channels.json" + }, + "serverVariables": { + "$ref": "http://asyncapi.com/definitions/2.6.0/serverVariables.json" + }, + "messages": { + "$ref": "http://asyncapi.com/definitions/2.6.0/messages.json" + }, + "securitySchemes": { + "type": "object", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SecurityScheme.json" + } + ] + } + } + }, + "parameters": { + "$ref": "http://asyncapi.com/definitions/2.6.0/parameters.json" + }, + "correlationIds": { + "type": "object", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/correlationId.json" + } + ] + } + } + }, + "operationTraits": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/operationTrait.json" + } + }, + "messageTraits": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/messageTrait.json" + } + }, + "serverBindings": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "channelBindings": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "operationBindings": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "messageBindings": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + } + }, + "examples": [ + { + "components": { + "schemas": { + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + } + }, + "servers": { + "development": { + "url": "{stage}.gigantic-server.com:{port}", + "description": "Development server", + "protocol": "amqp", + "protocolVersion": "0.9.1", + "variables": { + "stage": { + "$ref": "#/components/serverVariables/stage" + }, + "port": { + "$ref": "#/components/serverVariables/port" + } + } + } + }, + "serverVariables": { + "stage": { + "default": "demo", + "description": "This value is assigned by the service provider, in this example `gigantic-server.com`" + }, + "port": { + "enum": [ + "8883", + "8884" + ], + "default": "8883" + } + }, + "channels": { + "user/signedup": { + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignUp" + } + } + } + }, + "messages": { + "userSignUp": { + "summary": "Action to sign a user up.", + "description": "Multiline description of what this action does.\nHere you have another line.\n", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + } + ], + "headers": { + "type": "object", + "properties": { + "applicationInstanceId": { + "description": "Unique identifier for a given instance of the publishing application", + "type": "string" + } + } + }, + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/userCreate" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + } + } + }, + "parameters": { + "userId": { + "description": "Id of the user.", + "schema": { + "type": "string" + } + } + }, + "correlationIds": { + "default": { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + } + }, + "messageTraits": { + "commonHeaders": { + "headers": { + "type": "object", + "properties": { + "my-app-header": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + } + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/schemas.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/schemas.json", + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "description": "JSON objects describing schemas the API uses." + }, + "http://asyncapi.com/definitions/2.6.0/messages.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/messages.json", + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/message.json" + }, + "description": "JSON objects describing the messages being consumed and produced by the API." + }, + "http://asyncapi.com/definitions/2.6.0/SecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SecurityScheme.json", + "description": "Defines a security scheme that can be used by the operations.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/userPassword.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/apiKey.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/X509.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/symmetricEncryption.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/asymmetricEncryption.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/HTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flows.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/openIdConnect.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SaslSecurityScheme.json" + } + ], + "examples": [ + { + "type": "userPassword" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/userPassword.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/userPassword.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme. ", + "enum": [ + "userPassword" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "userPassword" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/apiKey.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/apiKey.json", + "type": "object", + "required": [ + "type", + "in" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "apiKey" + ] + }, + "in": { + "type": "string", + "description": "The location of the API key. ", + "enum": [ + "user", + "password" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "apiKey", + "in": "user" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/X509.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/X509.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "X509" + ], + "description": "The type of the security scheme." + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "X509" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/symmetricEncryption.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/symmetricEncryption.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "symmetricEncryption" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "symmetricEncryption" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/asymmetricEncryption.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/asymmetricEncryption.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "asymmetricEncryption" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/HTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/HTTPSecurityScheme.json", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/NonBearerHTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/BearerHTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/APIKeyHTTPSecurityScheme.json" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/NonBearerHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/NonBearerHTTPSecurityScheme.json", + "not": { + "type": "object", + "properties": { + "scheme": { + "type": "string", + "description": "A short description for security scheme.", + "enum": [ + "bearer" + ] + } + } + }, + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string", + "description": "The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235." + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + }, + "type": { + "type": "string", + "description": "The type of the security scheme. ", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/BearerHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/BearerHTTPSecurityScheme.json", + "type": "object", + "required": [ + "type", + "scheme" + ], + "properties": { + "scheme": { + "type": "string", + "description": "The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235.", + "enum": [ + "bearer" + ] + }, + "bearerFormat": { + "type": "string", + "description": "A hint to the client to identify how the bearer token is formatted." + }, + "type": { + "type": "string", + "description": "The type of the security scheme. ", + "enum": [ + "http" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/APIKeyHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/APIKeyHTTPSecurityScheme.json", + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme. ", + "enum": [ + "httpApiKey" + ] + }, + "name": { + "type": "string", + "description": "The name of the header, query or cookie parameter to be used." + }, + "in": { + "type": "string", + "description": "The location of the API key. ", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "httpApiKey", + "name": "api_key", + "in": "header" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/oauth2Flows.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/oauth2Flows.json", + "type": "object", + "description": "Allows configuration of the supported OAuth Flows.", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "description": "A short description for security scheme.", + "enum": [ + "oauth2" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + }, + "flows": { + "type": "object", + "properties": { + "implicit": { + "description": "Configuration for the OAuth Implicit flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json" + }, + { + "required": [ + "authorizationUrl", + "scopes" + ] + }, + { + "not": { + "required": [ + "tokenUrl" + ] + } + } + ] + }, + "password": { + "description": "Configuration for the OAuth Resource Owner Protected Credentials flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json" + }, + { + "required": [ + "tokenUrl", + "scopes" + ] + }, + { + "not": { + "required": [ + "authorizationUrl" + ] + } + } + ] + }, + "clientCredentials": { + "description": "Configuration for the OAuth Client Credentials flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json" + }, + { + "required": [ + "tokenUrl", + "scopes" + ] + }, + { + "not": { + "required": [ + "authorizationUrl" + ] + } + } + ] + }, + "authorizationCode": { + "description": "Configuration for the OAuth Authorization Code flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json" + }, + { + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ] + } + ] + } + }, + "additionalProperties": false + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + } + }, + "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json", + "type": "object", + "description": "Configuration details for a supported OAuth Flow", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri", + "description": "The authorization URL to be used for this flow. This MUST be in the form of an absolute URL." + }, + "tokenUrl": { + "type": "string", + "format": "uri", + "description": "The token URL to be used for this flow. This MUST be in the form of an absolute URL." + }, + "refreshUrl": { + "type": "string", + "format": "uri", + "description": "The URL to be used for obtaining refresh tokens. This MUST be in the form of an absolute URL." + }, + "scopes": { + "description": "The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it.", + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Scopes.json" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "authorizationCode": { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "tokenUrl": "https://example.com/api/oauth/token", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/oauth2Scopes.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/oauth2Scopes.json", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "http://asyncapi.com/definitions/2.6.0/openIdConnect.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/openIdConnect.json", + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "openIdConnect" + ] + }, + "description": { + "type": "string" + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/SaslSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SaslSecurityScheme.json", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SaslPlainSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SaslScramSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SaslGssapiSecurityScheme.json" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/SaslPlainSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SaslPlainSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme. Valid values", + "enum": [ + "plain" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/SaslScramSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SaslScramSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "scramSha256", + "scramSha512" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/SaslGssapiSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SaslGssapiSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "gssapi" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + } + }, + "description": "!!Auto generated!! \n Do not manually edit. " +} \ No newline at end of file diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.3.0.schema.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.3.0.schema.json new file mode 100644 index 0000000000..ed16148dc0 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.3.0.schema.json @@ -0,0 +1,8746 @@ +{ + "$id": "http://asyncapi.com/definitions/3.0.0/asyncapi.json", + "$schema": "http://json-schema.org/draft-07/schema", + "title": "AsyncAPI 3.0.0 schema.", + "type": "object", + "required": [ + "asyncapi", + "info" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "asyncapi": { + "type": "string", + "const": "3.0.0", + "description": "The AsyncAPI specification version of this document." + }, + "id": { + "type": "string", + "description": "A unique id representing the application.", + "format": "uri" + }, + "info": { + "$ref": "http://asyncapi.com/definitions/3.0.0/info.json" + }, + "servers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/servers.json" + }, + "defaultContentType": { + "type": "string", + "description": "Default content type to use when encoding/decoding a message's payload." + }, + "channels": { + "$ref": "http://asyncapi.com/definitions/3.0.0/channels.json" + }, + "operations": { + "$ref": "http://asyncapi.com/definitions/3.0.0/operations.json" + }, + "components": { + "$ref": "http://asyncapi.com/definitions/3.0.0/components.json" + } + }, + "definitions": { + "http://asyncapi.com/definitions/3.0.0/specificationExtension.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json", + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "http://asyncapi.com/definitions/3.0.0/info.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/info.json", + "type": "object", + "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." + }, + "termsOfService": { + "type": "string", + "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", + "format": "uri" + }, + "contact": { + "$ref": "http://asyncapi.com/definitions/3.0.0/contact.json" + }, + "license": { + "$ref": "http://asyncapi.com/definitions/3.0.0/license.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + } + }, + "examples": [ + { + "title": "AsyncAPI Sample App", + "version": "1.0.1", + "description": "This is a sample app.", + "termsOfService": "https://asyncapi.org/terms/", + "contact": { + "name": "API Support", + "url": "https://www.asyncapi.org/support", + "email": "support@asyncapi.org" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "externalDocs": { + "description": "Find more info here", + "url": "https://www.asyncapi.org" + }, + "tags": [ + { + "name": "e-commerce" + } + ] + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/contact.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/contact.json", + "type": "object", + "description": "Contact information for the exposed API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/license.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/license.json", + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/Reference.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/Reference.json", + "type": "object", + "description": "A simple object to allow referencing other components in the specification, internally and externally.", + "required": [ + "$ref" + ], + "properties": { + "$ref": { + "description": "The reference string.", + "$ref": "http://asyncapi.com/definitions/3.0.0/ReferenceObject.json" + } + }, + "examples": [ + { + "$ref": "#/components/schemas/Pet" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/ReferenceObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/ReferenceObject.json", + "type": "string", + "format": "uri-reference" + }, + "http://asyncapi.com/definitions/3.0.0/tag.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/tag.json", + "type": "object", + "description": "Allows adding metadata to a single tag.", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the tag." + }, + "description": { + "type": "string", + "description": "A short description for the tag. CommonMark syntax can be used for rich text representation." + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "user", + "description": "User-related messages" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/externalDocs.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/externalDocs.json", + "type": "object", + "additionalProperties": false, + "description": "Allows referencing an external resource for extended documentation.", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string", + "description": "A short description of the target documentation. CommonMark syntax can be used for rich text representation." + }, + "url": { + "type": "string", + "description": "The URL for the target documentation. This MUST be in the form of an absolute URL.", + "format": "uri" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "examples": [ + { + "description": "Find more info here", + "url": "https://example.com" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/servers.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/servers.json", + "description": "An object representing multiple servers.", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/server.json" + } + ] + }, + "examples": [ + { + "development": { + "host": "localhost:5672", + "description": "Development AMQP broker.", + "protocol": "amqp", + "protocolVersion": "0-9-1", + "tags": [ + { + "name": "env:development", + "description": "This environment is meant for developers to run their own tests." + } + ] + }, + "staging": { + "host": "rabbitmq-staging.in.mycompany.com:5672", + "description": "RabbitMQ broker for the staging environment.", + "protocol": "amqp", + "protocolVersion": "0-9-1", + "tags": [ + { + "name": "env:staging", + "description": "This environment is a replica of the production environment." + } + ] + }, + "production": { + "host": "rabbitmq.in.mycompany.com:5672", + "description": "RabbitMQ broker for the production environment.", + "protocol": "amqp", + "protocolVersion": "0-9-1", + "tags": [ + { + "name": "env:production", + "description": "This environment is the live environment available for final users." + } + ] + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/server.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/server.json", + "type": "object", + "description": "An object representing a message broker, a server or any other kind of computer program capable of sending and/or receiving data.", + "required": [ + "host", + "protocol" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "host": { + "type": "string", + "description": "The server host name. It MAY include the port. This field supports Server Variables. Variable substitutions will be made when a variable is named in {braces}." + }, + "pathname": { + "type": "string", + "description": "The path to a resource in the host. This field supports Server Variables. Variable substitutions will be made when a variable is named in {braces}." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the server." + }, + "summary": { + "type": "string", + "description": "A brief summary of the server." + }, + "description": { + "type": "string", + "description": "A longer description of the server. CommonMark is allowed." + }, + "protocol": { + "type": "string", + "description": "The protocol this server supports for connection." + }, + "protocolVersion": { + "type": "string", + "description": "An optional string describing the server. CommonMark syntax MAY be used for rich text representation." + }, + "variables": { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverVariables.json" + }, + "security": { + "$ref": "http://asyncapi.com/definitions/3.0.0/securityRequirements.json" + }, + "tags": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "host": "kafka.in.mycompany.com:9092", + "description": "Production Kafka broker.", + "protocol": "kafka", + "protocolVersion": "3.2" + }, + { + "host": "rabbitmq.in.mycompany.com:5672", + "pathname": "/production", + "protocol": "amqp", + "description": "Production RabbitMQ broker (uses the `production` vhost)." + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/serverVariables.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/serverVariables.json", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverVariable.json" + } + ] + } + }, + "http://asyncapi.com/definitions/3.0.0/serverVariable.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/serverVariable.json", + "type": "object", + "description": "An object representing a Server Variable for server URL template substitution.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "enum": { + "type": "array", + "description": "An enumeration of string values to be used if the substitution options are from a limited set.", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "default": { + "type": "string", + "description": "The default value to use for substitution, and to send, if an alternate value is not supplied." + }, + "description": { + "type": "string", + "description": "An optional description for the server variable. CommonMark syntax MAY be used for rich text representation." + }, + "examples": { + "type": "array", + "description": "An array of examples of the server variable.", + "items": { + "type": "string" + } + } + }, + "examples": [ + { + "host": "rabbitmq.in.mycompany.com:5672", + "pathname": "/{env}", + "protocol": "amqp", + "description": "RabbitMQ broker. Use the `env` variable to point to either `production` or `staging`.", + "variables": { + "env": { + "description": "Environment to connect to. It can be either `production` or `staging`.", + "enum": [ + "production", + "staging" + ] + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/securityRequirements.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/securityRequirements.json", + "description": "An array representing security requirements.", + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SecurityScheme.json" + } + ] + } + }, + "http://asyncapi.com/definitions/3.0.0/SecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SecurityScheme.json", + "description": "Defines a security scheme that can be used by the operations.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/userPassword.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/apiKey.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/X509.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/symmetricEncryption.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/asymmetricEncryption.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/HTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flows.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/openIdConnect.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SaslSecurityScheme.json" + } + ], + "examples": [ + { + "type": "userPassword" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/userPassword.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/userPassword.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "userPassword" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "userPassword" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/apiKey.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/apiKey.json", + "type": "object", + "required": [ + "type", + "in" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme", + "enum": [ + "apiKey" + ] + }, + "in": { + "type": "string", + "description": " The location of the API key.", + "enum": [ + "user", + "password" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme. CommonMark syntax MAY be used for rich text representation." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "apiKey", + "in": "user" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/X509.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/X509.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "X509" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "X509" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/symmetricEncryption.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/symmetricEncryption.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "symmetricEncryption" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "symmetricEncryption" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/asymmetricEncryption.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/asymmetricEncryption.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "asymmetricEncryption" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/HTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/HTTPSecurityScheme.json", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/NonBearerHTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/BearerHTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/APIKeyHTTPSecurityScheme.json" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/NonBearerHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/NonBearerHTTPSecurityScheme.json", + "not": { + "type": "object", + "properties": { + "scheme": { + "type": "string", + "description": "A short description for security scheme.", + "enum": [ + "bearer" + ] + } + } + }, + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string", + "description": "The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235." + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + }, + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/BearerHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/BearerHTTPSecurityScheme.json", + "type": "object", + "required": [ + "type", + "scheme" + ], + "properties": { + "scheme": { + "type": "string", + "description": "The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235.", + "enum": [ + "bearer" + ] + }, + "bearerFormat": { + "type": "string", + "description": "A hint to the client to identify how the bearer token is formatted. Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes." + }, + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "http" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme. CommonMark syntax MAY be used for rich text representation." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/APIKeyHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/APIKeyHTTPSecurityScheme.json", + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "httpApiKey" + ] + }, + "name": { + "type": "string", + "description": "The name of the header, query or cookie parameter to be used." + }, + "in": { + "type": "string", + "description": "The location of the API key", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme. CommonMark syntax MAY be used for rich text representation." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "httpApiKey", + "name": "api_key", + "in": "header" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/oauth2Flows.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/oauth2Flows.json", + "type": "object", + "description": "Allows configuration of the supported OAuth Flows.", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "oauth2" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + }, + "flows": { + "type": "object", + "properties": { + "implicit": { + "description": "Configuration for the OAuth Implicit flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json" + }, + { + "required": [ + "authorizationUrl", + "availableScopes" + ] + }, + { + "not": { + "required": [ + "tokenUrl" + ] + } + } + ] + }, + "password": { + "description": "Configuration for the OAuth Resource Owner Protected Credentials flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json" + }, + { + "required": [ + "tokenUrl", + "availableScopes" + ] + }, + { + "not": { + "required": [ + "authorizationUrl" + ] + } + } + ] + }, + "clientCredentials": { + "description": "Configuration for the OAuth Client Credentials flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json" + }, + { + "required": [ + "tokenUrl", + "availableScopes" + ] + }, + { + "not": { + "required": [ + "authorizationUrl" + ] + } + } + ] + }, + "authorizationCode": { + "description": "Configuration for the OAuth Authorization Code flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json" + }, + { + "required": [ + "authorizationUrl", + "tokenUrl", + "availableScopes" + ] + } + ] + } + }, + "additionalProperties": false + }, + "scopes": { + "type": "array", + "description": "List of the needed scope names.", + "items": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + } + }, + "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json", + "type": "object", + "description": "Configuration details for a supported OAuth Flow", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri", + "description": "The authorization URL to be used for this flow. This MUST be in the form of an absolute URL." + }, + "tokenUrl": { + "type": "string", + "format": "uri", + "description": "The token URL to be used for this flow. This MUST be in the form of an absolute URL." + }, + "refreshUrl": { + "type": "string", + "format": "uri", + "description": "The URL to be used for obtaining refresh tokens. This MUST be in the form of an absolute URL." + }, + "availableScopes": { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Scopes.json", + "description": "The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "tokenUrl": "https://example.com/api/oauth/token", + "availableScopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/oauth2Scopes.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/oauth2Scopes.json", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "http://asyncapi.com/definitions/3.0.0/openIdConnect.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/openIdConnect.json", + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "openIdConnect" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme. CommonMark syntax MAY be used for rich text representation." + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri", + "description": "OpenId Connect URL to discover OAuth2 configuration values. This MUST be in the form of an absolute URL." + }, + "scopes": { + "type": "array", + "description": "List of the needed scope names. An empty array means no scopes are needed.", + "items": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/SaslSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SaslSecurityScheme.json", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SaslPlainSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SaslScramSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SaslGssapiSecurityScheme.json" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/SaslPlainSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SaslPlainSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme. Valid values", + "enum": [ + "plain" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/SaslScramSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SaslScramSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "scramSha256", + "scramSha512" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/SaslGssapiSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SaslGssapiSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "gssapi" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/serverBindingsObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/serverBindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for a server.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "http": {}, + "ws": {}, + "amqp": {}, + "amqp1": {}, + "mqtt": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/server.json" + } + } + ] + }, + "kafka": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.3.0/server.json" + } + } + ] + }, + "anypointmq": {}, + "nats": {}, + "jms": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/server.json" + } + } + ] + }, + "sns": {}, + "sqs": {}, + "stomp": {}, + "redis": {}, + "ibmmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/server.json" + } + } + ] + }, + "solace": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0", + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.4.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.4.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.3.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.2.0/server.json" + } + } + ] + }, + "googlepubsub": {}, + "pulsar": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/pulsar/0.1.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/pulsar/0.1.0/server.json" + } + } + ] + } + } + }, + "http://asyncapi.com/bindings/mqtt/0.2.0/server.json": { + "$id": "http://asyncapi.com/bindings/mqtt/0.2.0/server.json", + "title": "Server Schema", + "description": "This object contains information about the server representation in MQTT.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "clientId": { + "type": "string", + "description": "The client identifier." + }, + "cleanSession": { + "type": "boolean", + "description": "Whether to create a persistent connection or not. When 'false', the connection will be persistent. This is called clean start in MQTTv5." + }, + "lastWill": { + "type": "object", + "description": "Last Will and Testament configuration.", + "properties": { + "topic": { + "type": "string", + "description": "The topic where the Last Will and Testament message will be sent." + }, + "qos": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "Defines how hard the broker/client will try to ensure that the Last Will and Testament message is received. Its value MUST be either 0, 1 or 2." + }, + "message": { + "type": "string", + "description": "Last Will message." + }, + "retain": { + "type": "boolean", + "description": "Whether the broker should retain the Last Will and Testament message or not." + } + } + }, + "keepAlive": { + "type": "integer", + "description": "Interval in seconds of the longest period of time the broker and the client can endure without sending a message." + }, + "sessionExpiryInterval": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "Interval time in seconds or a Schema Object containing the definition of the interval. The broker maintains a session for a disconnected client until this interval expires." + }, + "maximumPacketSize": { + "oneOf": [ + { + "type": "integer", + "minimum": 1, + "maximum": 4294967295 + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "Number of bytes or a Schema Object representing the Maximum Packet Size the Client is willing to accept." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "clientId": "guest", + "cleanSession": true, + "lastWill": { + "topic": "/last-wills", + "qos": 2, + "message": "Guest gone offline.", + "retain": false + }, + "keepAlive": 60, + "sessionExpiryInterval": 120, + "maximumPacketSize": 1024, + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/schema.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "The Schema Object allows the definition of input and output data types. These types can be objects, but also primitives and arrays. This object is a superset of the JSON Schema Specification Draft 07. The empty schema (which allows any instance to validate) MAY be represented by the boolean value true and a schema which allows no instance to validate MAY be represented by the boolean value false.", + "allOf": [ + { + "$ref": "http://json-schema.org/draft-07/schema#" + }, + { + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "type": "boolean" + } + ], + "default": {} + }, + "items": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + } + ], + "default": {} + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + }, + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + }, + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + }, + "not": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "default": {} + }, + "propertyNames": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "contains": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "discriminator": { + "type": "string", + "description": "Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. The property name used MUST be defined at this schema and it MUST be in the required property list. When used, the value MUST be the name of this schema or any schema that inherits it. See Composition and Inheritance for more details." + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "deprecated": { + "type": "boolean", + "description": "Specifies that a schema is deprecated and SHOULD be transitioned out of usage. Default value is false.", + "default": false + } + } + } + ] + }, + "http://json-schema.org/draft-07/schema": { + "$id": "http://json-schema.org/draft-07/schema", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/nonNegativeInteger" + }, + { + "default": 0 + } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "default": [] + } + }, + "type": [ + "object", + "boolean" + ], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#" + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#" + }, + "maxProperties": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#" + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "propertyNames": { + "format": "regex" + }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#" + }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#" + }, + "then": { + "$ref": "#" + }, + "else": { + "$ref": "#" + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + } + }, + "default": true + }, + "http://asyncapi.com/bindings/kafka/0.4.0/server.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.4.0/server.json", + "title": "Server Schema", + "description": "This object contains server connection information to a Kafka broker. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "schemaRegistryUrl": { + "type": "string", + "description": "API URL for the Schema Registry used when producing Kafka messages (if a Schema Registry was used)." + }, + "schemaRegistryVendor": { + "type": "string", + "description": "The vendor of the Schema Registry and Kafka serdes library that should be used." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "schemaRegistryUrl": "https://my-schema-registry.com", + "schemaRegistryVendor": "confluent", + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.3.0/server.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.3.0/server.json", + "title": "Server Schema", + "description": "This object contains server connection information to a Kafka broker. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "schemaRegistryUrl": { + "type": "string", + "description": "API URL for the Schema Registry used when producing Kafka messages (if a Schema Registry was used)." + }, + "schemaRegistryVendor": { + "type": "string", + "description": "The vendor of the Schema Registry and Kafka serdes library that should be used." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "schemaRegistryUrl": "https://my-schema-registry.com", + "schemaRegistryVendor": "confluent", + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/jms/0.0.1/server.json": { + "$id": "http://asyncapi.com/bindings/jms/0.0.1/server.json", + "title": "Server Schema", + "description": "This object contains configuration for describing a JMS broker as an AsyncAPI server. This objects only contains configuration that can not be provided in the AsyncAPI standard server object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "required": [ + "jmsConnectionFactory" + ], + "properties": { + "jmsConnectionFactory": { + "type": "string", + "description": "The classname of the ConnectionFactory implementation for the JMS Provider." + }, + "properties": { + "type": "array", + "items": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/server.json#/definitions/property" + }, + "description": "Additional properties to set on the JMS ConnectionFactory implementation for the JMS Provider." + }, + "clientID": { + "type": "string", + "description": "A client identifier for applications that use this JMS connection factory. If the Client ID Policy is set to 'Restricted' (the default), then configuring a Client ID on the ConnectionFactory prevents more than one JMS client from using a connection from this factory." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "definitions": { + "property": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of a property" + }, + "value": { + "type": [ + "string", + "boolean", + "number", + "null" + ], + "description": "The name of a property" + } + } + } + }, + "examples": [ + { + "jmsConnectionFactory": "org.apache.activemq.ActiveMQConnectionFactory", + "properties": [ + { + "name": "disableTimeStampsByDefault", + "value": false + } + ], + "clientID": "my-application-1", + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/ibmmq/0.1.0/server.json": { + "$id": "http://asyncapi.com/bindings/ibmmq/0.1.0/server.json", + "title": "IBM MQ server bindings object", + "description": "This object contains server connection information about the IBM MQ server, referred to as an IBM MQ queue manager. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "groupId": { + "type": "string", + "description": "Defines a logical group of IBM MQ server objects. This is necessary to specify multi-endpoint configurations used in high availability deployments. If omitted, the server object is not part of a group." + }, + "ccdtQueueManagerName": { + "type": "string", + "default": "*", + "description": "The name of the IBM MQ queue manager to bind to in the CCDT file." + }, + "cipherSpec": { + "type": "string", + "description": "The recommended cipher specification used to establish a TLS connection between the client and the IBM MQ queue manager. More information on SSL/TLS cipher specifications supported by IBM MQ can be found on this page in the IBM MQ Knowledge Center." + }, + "multiEndpointServer": { + "type": "boolean", + "default": false, + "description": "If 'multiEndpointServer' is 'true' then multiple connections can be workload balanced and applications should not make assumptions as to where messages are processed. Where message ordering, or affinity to specific message resources is necessary, a single endpoint ('multiEndpointServer' = 'false') may be required." + }, + "heartBeatInterval": { + "type": "integer", + "minimum": 0, + "maximum": 999999, + "default": 300, + "description": "The recommended value (in seconds) for the heartbeat sent to the queue manager during periods of inactivity. A value of zero means that no heart beats are sent. A value of 1 means that the client will use the value defined by the queue manager. More information on heart beat interval can be found on this page in the IBM MQ Knowledge Center." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "groupId": "PRODCLSTR1", + "cipherSpec": "ANY_TLS12_OR_HIGHER", + "bindingVersion": "0.1.0" + }, + { + "groupId": "PRODCLSTR1", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/solace/0.4.0/server.json": { + "$id": "http://asyncapi.com/bindings/solace/0.4.0/server.json", + "title": "Solace server bindings object", + "description": "This object contains server connection information about the Solace broker. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "msgVpn": { + "type": "string", + "description": "The name of the Virtual Private Network to connect to on the Solace broker." + }, + "clientName": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "description": "A unique client name to use to register to the appliance. If specified, it must be a valid Topic name, and a maximum of 160 bytes in length when encoded as UTF-8." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "msgVpn": "ProdVPN", + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/solace/0.3.0/server.json": { + "$id": "http://asyncapi.com/bindings/solace/0.3.0/server.json", + "title": "Solace server bindings object", + "description": "This object contains server connection information about the Solace broker. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "msgVpn": { + "type": "string", + "description": "The name of the Virtual Private Network to connect to on the Solace broker." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "msgVpn": "ProdVPN", + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/solace/0.2.0/server.json": { + "$id": "http://asyncapi.com/bindings/solace/0.2.0/server.json", + "title": "Solace server bindings object", + "description": "This object contains server connection information about the Solace broker. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "msvVpn": { + "type": "string", + "description": "The name of the Virtual Private Network to connect to on the Solace broker." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "msgVpn": "ProdVPN", + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/pulsar/0.1.0/server.json": { + "$id": "http://asyncapi.com/bindings/pulsar/0.1.0/server.json", + "title": "Server Schema", + "description": "This object contains server information of Pulsar broker, which covers cluster and tenant admin configuration. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "tenant": { + "type": "string", + "description": "The pulsar tenant. If omitted, 'public' MUST be assumed." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "tenant": "contoso", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/channels.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/channels.json", + "type": "object", + "description": "An object containing all the Channel Object definitions the Application MUST use during runtime.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/channel.json" + } + ] + }, + "examples": [ + { + "userSignedUp": { + "address": "user.signedup", + "messages": { + "userSignedUp": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/channel.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/channel.json", + "type": "object", + "description": "Describes a shared communication channel.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "address": { + "type": [ + "string", + "null" + ], + "description": "An optional string representation of this channel's address. The address is typically the \"topic name\", \"routing key\", \"event type\", or \"path\". When `null` or absent, it MUST be interpreted as unknown. This is useful when the address is generated dynamically at runtime or can't be known upfront. It MAY contain Channel Address Expressions." + }, + "messages": { + "$ref": "http://asyncapi.com/definitions/3.0.0/channelMessages.json" + }, + "parameters": { + "$ref": "http://asyncapi.com/definitions/3.0.0/parameters.json" + }, + "title": { + "type": "string", + "description": "A human-friendly title for the channel." + }, + "summary": { + "type": "string", + "description": "A brief summary of the channel." + }, + "description": { + "type": "string", + "description": "A longer description of the channel. CommonMark is allowed." + }, + "servers": { + "type": "array", + "description": "The references of the servers on which this channel is available. If absent or empty then this channel must be available on all servers.", + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + "uniqueItems": true + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping of channels.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/channelBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "address": "users.{userId}", + "title": "Users channel", + "description": "This channel is used to exchange messages about user events.", + "messages": { + "userSignedUp": { + "$ref": "#/components/messages/userSignedUp" + }, + "userCompletedOrder": { + "$ref": "#/components/messages/userCompletedOrder" + } + }, + "parameters": { + "userId": { + "$ref": "#/components/parameters/userId" + } + }, + "servers": [ + { + "$ref": "#/servers/rabbitmqInProd" + }, + { + "$ref": "#/servers/rabbitmqInStaging" + } + ], + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "exclusive": true + } + } + }, + "tags": [ + { + "name": "user", + "description": "User-related messages" + } + ], + "externalDocs": { + "description": "Find more info here", + "url": "https://example.com" + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/channelMessages.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/channelMessages.json", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageObject.json" + } + ] + }, + "description": "A map of the messages that will be sent to this channel by any application at any time. **Every message sent to this channel MUST be valid against one, and only one, of the message objects defined in this map.**" + }, + "http://asyncapi.com/definitions/3.0.0/messageObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/messageObject.json", + "type": "object", + "description": "Describes a message received on a given channel and operation.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "contentType": { + "type": "string", + "description": "The content type to use when encoding/decoding a message's payload. The value MUST be a specific media type (e.g. application/json). When omitted, the value MUST be the one specified on the defaultContentType field." + }, + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/anySchema.json" + }, + "payload": { + "$ref": "http://asyncapi.com/definitions/3.0.0/anySchema.json" + }, + "correlationId": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/correlationId.json" + } + ] + }, + "tags": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the message." + }, + "name": { + "type": "string", + "description": "Name of the message." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the message." + }, + "description": { + "type": "string", + "description": "A longer description of the message. CommonMark is allowed." + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "description": "List of examples.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "payload" + ] + }, + { + "required": [ + "headers" + ] + } + ], + "properties": { + "name": { + "type": "string", + "description": "Machine readable name of the message example." + }, + "summary": { + "type": "string", + "description": "A brief summary of the message example." + }, + "headers": { + "type": "object", + "description": "Example of the application headers. It can be of any type." + }, + "payload": { + "description": "Example of the message payload. It can be of any type." + } + } + } + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json" + } + ] + }, + "traits": { + "type": "array", + "description": "A list of traits to apply to the message object. Traits MUST be merged using traits merge mechanism. The resulting object MUST be a valid Message Object.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageTrait.json" + }, + { + "type": "array", + "items": [ + { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageTrait.json" + } + ] + }, + { + "type": "object", + "additionalItems": true + } + ] + } + ] + } + } + }, + "examples": [ + { + "messageId": "userSignup", + "name": "UserSignup", + "title": "User signup", + "summary": "Action to sign a user up.", + "description": "A longer description", + "contentType": "application/json", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "headers": { + "type": "object", + "properties": { + "correlationId": { + "description": "Correlation ID set by application", + "type": "string" + }, + "applicationInstanceId": { + "description": "Unique identifier for a given instance of the publishing application", + "type": "string" + } + } + }, + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/userCreate" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + }, + "correlationId": { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + }, + "traits": [ + { + "$ref": "#/components/messageTraits/commonHeaders" + } + ], + "examples": [ + { + "name": "SimpleSignup", + "summary": "A simple UserSignup example message", + "headers": { + "correlationId": "my-correlation-id", + "applicationInstanceId": "myInstanceId" + }, + "payload": { + "user": { + "someUserKey": "someUserValue" + }, + "signup": { + "someSignupKey": "someSignupValue" + } + } + } + ] + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/anySchema.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/anySchema.json", + "if": { + "required": [ + "schema" + ] + }, + "then": { + "$ref": "http://asyncapi.com/definitions/3.0.0/multiFormatSchema.json" + }, + "else": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "description": "An object representing either a schema or a multiFormatSchema based on the existence of the 'schema' property. If the property 'schema' is present, use the multi-format schema. Use the default AsyncAPI Schema otherwise." + }, + "http://asyncapi.com/definitions/3.0.0/multiFormatSchema.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/multiFormatSchema.json", + "description": "The Multi Format Schema Object represents a schema definition. It differs from the Schema Object in that it supports multiple schema formats or languages (e.g., JSON Schema, Avro, etc.).", + "if": { + "not": { + "type": "object" + } + }, + "then": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "else": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "schemaFormat": { + "description": "A string containing the name of the schema format that is used to define the information. If schemaFormat is missing, it MUST default to application/vnd.aai.asyncapi+json;version={{asyncapi}} where {{asyncapi}} matches the AsyncAPI Version String. In such a case, this would make the Multi Format Schema Object equivalent to the Schema Object. When using Reference Object within the schema, the schemaFormat of the resource being referenced MUST match the schemaFormat of the schema that contains the initial reference. For example, if you reference Avro schema, then schemaFormat of referencing resource and the resource being reference MUST match.", + "anyOf": [ + { + "type": "string" + }, + { + "description": "All the schema formats tooling MUST support", + "enum": [ + "application/schema+json;version=draft-07", + "application/schema+yaml;version=draft-07", + "application/vnd.aai.asyncapi;version=3.0.0", + "application/vnd.aai.asyncapi+json;version=3.0.0", + "application/vnd.aai.asyncapi+yaml;version=3.0.0" + ] + }, + { + "description": "All the schema formats tools are RECOMMENDED to support", + "enum": [ + "application/vnd.oai.openapi;version=3.0.0", + "application/vnd.oai.openapi+json;version=3.0.0", + "application/vnd.oai.openapi+yaml;version=3.0.0", + "application/vnd.apache.avro;version=1.9.0", + "application/vnd.apache.avro+json;version=1.9.0", + "application/vnd.apache.avro+yaml;version=1.9.0", + "application/raml+yaml;version=1.0" + ] + } + ] + }, + "schema": { + "description": "Definition of the message payload. It can be of any type but defaults to Schema Object. It MUST match the schema format defined in schemaFormat, including the encoding type. E.g., Avro should be inlined as either a YAML or JSON object instead of as a string to be parsed as YAML or JSON. Non-JSON-based schemas (e.g., Protobuf or XSD) MUST be inlined as a string." + } + }, + "allOf": [ + { + "if": { + "not": { + "description": "If no schemaFormat has been defined, default to schema or reference", + "required": [ + "schemaFormat" + ] + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + ] + } + } + } + }, + { + "if": { + "description": "If schemaFormat has been defined check if it's one of the AsyncAPI Schema Object formats", + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.aai.asyncapi;version=2.0.0", + "application/vnd.aai.asyncapi+json;version=2.0.0", + "application/vnd.aai.asyncapi+yaml;version=2.0.0", + "application/vnd.aai.asyncapi;version=2.1.0", + "application/vnd.aai.asyncapi+json;version=2.1.0", + "application/vnd.aai.asyncapi+yaml;version=2.1.0", + "application/vnd.aai.asyncapi;version=2.2.0", + "application/vnd.aai.asyncapi+json;version=2.2.0", + "application/vnd.aai.asyncapi+yaml;version=2.2.0", + "application/vnd.aai.asyncapi;version=2.3.0", + "application/vnd.aai.asyncapi+json;version=2.3.0", + "application/vnd.aai.asyncapi+yaml;version=2.3.0", + "application/vnd.aai.asyncapi;version=2.4.0", + "application/vnd.aai.asyncapi+json;version=2.4.0", + "application/vnd.aai.asyncapi+yaml;version=2.4.0", + "application/vnd.aai.asyncapi;version=2.5.0", + "application/vnd.aai.asyncapi+json;version=2.5.0", + "application/vnd.aai.asyncapi+yaml;version=2.5.0", + "application/vnd.aai.asyncapi;version=2.6.0", + "application/vnd.aai.asyncapi+json;version=2.6.0", + "application/vnd.aai.asyncapi+yaml;version=2.6.0", + "application/vnd.aai.asyncapi;version=3.0.0", + "application/vnd.aai.asyncapi+json;version=3.0.0", + "application/vnd.aai.asyncapi+yaml;version=3.0.0" + ] + } + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + ] + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/schema+json;version=draft-07", + "application/schema+yaml;version=draft-07" + ] + } + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://json-schema.org/draft-07/schema" + } + ] + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.oai.openapi;version=3.0.0", + "application/vnd.oai.openapi+json;version=3.0.0", + "application/vnd.oai.openapi+yaml;version=3.0.0" + ] + } + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/openapiSchema_3_0.json" + } + ] + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.apache.avro;version=1.9.0", + "application/vnd.apache.avro+json;version=1.9.0", + "application/vnd.apache.avro+yaml;version=1.9.0" + ] + } + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/avroSchema_v1.json" + } + ] + } + } + } + } + ] + } + }, + "http://asyncapi.com/definitions/3.0.0/openapiSchema_3_0.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/openapiSchema_3_0.json", + "type": "object", + "definitions": { + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + } + }, + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": true, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": true, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": true + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/avroSchema_v1.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/avroSchema_v1.json", + "definitions": { + "avroSchema": { + "title": "Avro Schema", + "description": "Root Schema", + "oneOf": [ + { + "$ref": "#/definitions/types" + } + ] + }, + "types": { + "title": "Avro Types", + "description": "Allowed Avro types", + "oneOf": [ + { + "$ref": "#/definitions/primitiveType" + }, + { + "$ref": "#/definitions/primitiveTypeWithMetadata" + }, + { + "$ref": "#/definitions/customTypeReference" + }, + { + "$ref": "#/definitions/avroRecord" + }, + { + "$ref": "#/definitions/avroEnum" + }, + { + "$ref": "#/definitions/avroArray" + }, + { + "$ref": "#/definitions/avroMap" + }, + { + "$ref": "#/definitions/avroFixed" + }, + { + "$ref": "#/definitions/avroUnion" + } + ] + }, + "primitiveType": { + "title": "Primitive Type", + "description": "Basic type primitives.", + "type": "string", + "enum": [ + "null", + "boolean", + "int", + "long", + "float", + "double", + "bytes", + "string" + ] + }, + "primitiveTypeWithMetadata": { + "title": "Primitive Type With Metadata", + "description": "A primitive type with metadata attached.", + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/primitiveType" + } + }, + "required": [ + "type" + ] + }, + "customTypeReference": { + "title": "Custom Type", + "description": "Reference to a ComplexType", + "not": { + "$ref": "#/definitions/primitiveType" + }, + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$" + }, + "avroUnion": { + "title": "Union", + "description": "A Union of types", + "type": "array", + "items": { + "$ref": "#/definitions/avroSchema" + }, + "minItems": 1 + }, + "avroField": { + "title": "Field", + "description": "A field within a Record", + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/name" + }, + "type": { + "$ref": "#/definitions/types" + }, + "doc": { + "type": "string" + }, + "default": true, + "order": { + "enum": [ + "ascending", + "descending", + "ignore" + ] + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + } + }, + "required": [ + "name", + "type" + ] + }, + "avroRecord": { + "title": "Record", + "description": "A Record", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "record" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/avroField" + } + } + }, + "required": [ + "type", + "name", + "fields" + ] + }, + "avroEnum": { + "title": "Enum", + "description": "An enumeration", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "enum" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "symbols": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + } + }, + "required": [ + "type", + "name", + "symbols" + ] + }, + "avroArray": { + "title": "Array", + "description": "An array", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "array" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "items": { + "$ref": "#/definitions/types" + } + }, + "required": [ + "type", + "items" + ] + }, + "avroMap": { + "title": "Map", + "description": "A map of values", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "map" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "values": { + "$ref": "#/definitions/types" + } + }, + "required": [ + "type", + "values" + ] + }, + "avroFixed": { + "title": "Fixed", + "description": "A fixed sized array of bytes", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "fixed" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "size": { + "type": "number" + } + }, + "required": [ + "type", + "name", + "size" + ] + }, + "name": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" + }, + "namespace": { + "type": "string", + "pattern": "^([A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*)*$" + } + }, + "description": "Json-Schema definition for Avro AVSC files.", + "oneOf": [ + { + "$ref": "#/definitions/avroSchema" + } + ], + "title": "Avro Schema Definition" + }, + "http://asyncapi.com/definitions/3.0.0/correlationId.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/correlationId.json", + "type": "object", + "description": "An object that specifies an identifier at design time that can used for message tracing and correlation.", + "required": [ + "location" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A optional description of the correlation ID. GitHub Flavored Markdown is allowed." + }, + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the correlation ID", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + } + }, + "examples": [ + { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for a message.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "http": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.2.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.2.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.3.0/message.json" + } + } + ] + }, + "ws": {}, + "amqp": { + "properties": { + "bindingVersion": { + "enum": [ + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/message.json" + } + } + ] + }, + "amqp1": {}, + "mqtt": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/message.json" + } + } + ] + }, + "kafka": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.3.0/message.json" + } + } + ] + }, + "anypointmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/anypointmq/0.0.1/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/anypointmq/0.0.1/message.json" + } + } + ] + }, + "nats": {}, + "jms": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/message.json" + } + } + ] + }, + "sns": {}, + "sqs": {}, + "stomp": {}, + "redis": {}, + "ibmmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/message.json" + } + } + ] + }, + "solace": {}, + "googlepubsub": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/googlepubsub/0.2.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/googlepubsub/0.2.0/message.json" + } + } + ] + } + } + }, + "http://asyncapi.com/bindings/http/0.2.0/message.json": { + "$id": "http://asyncapi.com/bindings/http/0.2.0/message.json", + "title": "HTTP message bindings object", + "description": "This object contains information about the message representation in HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "\tA Schema object containing the definitions for HTTP-specific headers. This schema MUST be of type 'object' and have a 'properties' key." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "headers": { + "type": "object", + "properties": { + "Content-Type": { + "type": "string", + "enum": [ + "application/json" + ] + } + } + }, + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/http/0.3.0/message.json": { + "$id": "http://asyncapi.com/bindings/http/0.3.0/message.json", + "title": "HTTP message bindings object", + "description": "This object contains information about the message representation in HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "\tA Schema object containing the definitions for HTTP-specific headers. This schema MUST be of type 'object' and have a 'properties' key." + }, + "statusCode": { + "type": "number", + "description": "The HTTP response status code according to [RFC 9110](https://httpwg.org/specs/rfc9110.html#overview.of.status.codes). `statusCode` is only relevant for messages referenced by the [Operation Reply Object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#operationReplyObject), as it defines the status code for the response. In all other cases, this value can be safely ignored." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "headers": { + "type": "object", + "properties": { + "Content-Type": { + "type": "string", + "enum": [ + "application/json" + ] + } + } + }, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/amqp/0.3.0/message.json": { + "$id": "http://asyncapi.com/bindings/amqp/0.3.0/message.json", + "title": "AMQP message bindings object", + "description": "This object contains information about the message representation in AMQP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "contentEncoding": { + "type": "string", + "description": "A MIME encoding for the message content." + }, + "messageType": { + "type": "string", + "description": "Application-specific message type." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "contentEncoding": "gzip", + "messageType": "user.signup", + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/mqtt/0.2.0/message.json": { + "$id": "http://asyncapi.com/bindings/mqtt/0.2.0/message.json", + "title": "MQTT message bindings object", + "description": "This object contains information about the message representation in MQTT.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "payloadFormatIndicator": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "description": "1 indicates that the payload is UTF-8 encoded character data. 0 indicates that the payload format is unspecified.", + "default": 0 + }, + "correlationData": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "Correlation Data is used by the sender of the request message to identify which request the response message is for when it is received." + }, + "contentType": { + "type": "string", + "description": "String describing the content type of the message payload. This should not conflict with the contentType field of the associated AsyncAPI Message object." + }, + "responseTopic": { + "oneOf": [ + { + "type": "string", + "format": "uri-template", + "minLength": 1 + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "The topic (channel URI) to be used for a response message." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "bindingVersion": "0.2.0" + }, + { + "contentType": "application/json", + "correlationData": { + "type": "string", + "format": "uuid" + }, + "responseTopic": "application/responses", + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.4.0/message.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.4.0/message.json", + "title": "Message Schema", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "key": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/avroSchema_v1.json" + } + ], + "description": "The message key." + }, + "schemaIdLocation": { + "type": "string", + "description": "If a Schema Registry is used when performing this operation, tells where the id of schema is stored.", + "enum": [ + "header", + "payload" + ] + }, + "schemaIdPayloadEncoding": { + "type": "string", + "description": "Number of bytes or vendor specific values when schema id is encoded in payload." + }, + "schemaLookupStrategy": { + "type": "string", + "description": "Freeform string for any naming strategy class to use. Clients should default to the vendor default if not supplied." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "key": { + "type": "string", + "enum": [ + "myKey" + ] + }, + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "apicurio-new", + "schemaLookupStrategy": "TopicIdStrategy", + "bindingVersion": "0.4.0" + }, + { + "key": { + "$ref": "path/to/user-create.avsc#/UserCreate" + }, + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "4", + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.3.0/message.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.3.0/message.json", + "title": "Message Schema", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "key": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "The message key." + }, + "schemaIdLocation": { + "type": "string", + "description": "If a Schema Registry is used when performing this operation, tells where the id of schema is stored.", + "enum": [ + "header", + "payload" + ] + }, + "schemaIdPayloadEncoding": { + "type": "string", + "description": "Number of bytes or vendor specific values when schema id is encoded in payload." + }, + "schemaLookupStrategy": { + "type": "string", + "description": "Freeform string for any naming strategy class to use. Clients should default to the vendor default if not supplied." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "key": { + "type": "string", + "enum": [ + "myKey" + ] + }, + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "apicurio-new", + "schemaLookupStrategy": "TopicIdStrategy", + "bindingVersion": "0.3.0" + }, + { + "key": { + "$ref": "path/to/user-create.avsc#/UserCreate" + }, + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "4", + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/anypointmq/0.0.1/message.json": { + "$id": "http://asyncapi.com/bindings/anypointmq/0.0.1/message.json", + "title": "Anypoint MQ message bindings object", + "description": "This object contains configuration for describing an Anypoint MQ message as an AsyncAPI message. This objects only contains configuration that can not be provided in the AsyncAPI standard message object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "headers": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "A Schema object containing the definitions for Anypoint MQ-specific headers (protocol headers). This schema MUST be of type 'object' and have a 'properties' key. Examples of Anypoint MQ protocol headers are 'messageId' and 'messageGroupId'." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "headers": { + "type": "object", + "properties": { + "messageId": { + "type": "string" + } + } + }, + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/jms/0.0.1/message.json": { + "$id": "http://asyncapi.com/bindings/jms/0.0.1/message.json", + "title": "Message Schema", + "description": "This object contains configuration for describing a JMS message as an AsyncAPI message. This objects only contains configuration that can not be provided in the AsyncAPI standard message object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "A Schema object containing the definitions for JMS headers (protocol headers). This schema MUST be of type 'object' and have a 'properties' key. Examples of JMS protocol headers are 'JMSMessageID', 'JMSTimestamp', and 'JMSCorrelationID'." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "headers": { + "type": "object", + "required": [ + "JMSMessageID" + ], + "properties": { + "JMSMessageID": { + "type": [ + "string", + "null" + ], + "description": "A unique message identifier. This may be set by your JMS Provider on your behalf." + }, + "JMSTimestamp": { + "type": "integer", + "description": "The time the message was sent. This may be set by your JMS Provider on your behalf. The time the message was sent. The value of the timestamp is the amount of time, measured in milliseconds, that has elapsed since midnight, January 1, 1970, UTC." + }, + "JMSDeliveryMode": { + "type": "string", + "enum": [ + "PERSISTENT", + "NON_PERSISTENT" + ], + "default": "PERSISTENT", + "description": "Denotes the delivery mode for the message. This may be set by your JMS Provider on your behalf." + }, + "JMSPriority": { + "type": "integer", + "default": 4, + "description": "The priority of the message. This may be set by your JMS Provider on your behalf." + }, + "JMSExpires": { + "type": "integer", + "description": "The time at which the message expires. This may be set by your JMS Provider on your behalf. A value of zero means that the message does not expire. Any non-zero value is the amount of time, measured in milliseconds, that has elapsed since midnight, January 1, 1970, UTC, at which the message will expire." + }, + "JMSType": { + "type": [ + "string", + "null" + ], + "description": "The type of message. Some JMS providers use a message repository that contains the definitions of messages sent by applications. The 'JMSType' header field may reference a message's definition in the provider's repository. The JMS API does not define a standard message definition repository, nor does it define a naming policy for the definitions it contains. Some messaging systems require that a message type definition for each application message be created and that each message specify its type. In order to work with such JMS providers, JMS clients should assign a value to 'JMSType', whether the application makes use of it or not. This ensures that the field is properly set for those providers that require it." + }, + "JMSCorrelationID": { + "type": [ + "string", + "null" + ], + "description": "The correlation identifier of the message. A client can use the 'JMSCorrelationID' header field to link one message with another. A typical use is to link a response message with its request message. Since each message sent by a JMS provider is assigned a message ID value, it is convenient to link messages via message ID, such message ID values must start with the 'ID:' prefix. Conversely, application-specified values must not start with the 'ID:' prefix; this is reserved for provider-generated message ID values." + }, + "JMSReplyTo": { + "type": "string", + "description": "The queue or topic that the message sender expects replies to." + } + } + }, + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/ibmmq/0.1.0/message.json": { + "$id": "http://asyncapi.com/bindings/ibmmq/0.1.0/message.json", + "title": "IBM MQ message bindings object", + "description": "This object contains information about the message representation in IBM MQ.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "jms", + "binary" + ], + "default": "string", + "description": "The type of the message." + }, + "headers": { + "type": "string", + "description": "Defines the IBM MQ message headers to include with this message. More than one header can be specified as a comma separated list. Supporting information on IBM MQ message formats can be found on this [page](https://www.ibm.com/docs/en/ibm-mq/9.2?topic=mqmd-format-mqchar8) in the IBM MQ Knowledge Center." + }, + "description": { + "type": "string", + "description": "Provides additional information for application developers: describes the message type or format." + }, + "expiry": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "The recommended setting the client should use for the TTL (Time-To-Live) of the message. This is a period of time expressed in milliseconds and set by the application that puts the message. 'expiry' values are API dependant e.g., MQI and JMS use different units of time and default values for 'unlimited'. General information on IBM MQ message expiry can be found on this [page](https://www.ibm.com/docs/en/ibm-mq/9.2?topic=mqmd-expiry-mqlong) in the IBM MQ Knowledge Center." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + } + }, + "oneOf": [ + { + "properties": { + "type": { + "const": "binary" + } + } + }, + { + "properties": { + "type": { + "const": "jms" + } + }, + "not": { + "required": [ + "headers" + ] + } + }, + { + "properties": { + "type": { + "const": "string" + } + }, + "not": { + "required": [ + "headers" + ] + } + } + ], + "examples": [ + { + "type": "string", + "bindingVersion": "0.1.0" + }, + { + "type": "jms", + "description": "JMS stream message", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/googlepubsub/0.2.0/message.json": { + "$id": "http://asyncapi.com/bindings/googlepubsub/0.2.0/message.json", + "title": "Cloud Pub/Sub Channel Schema", + "description": "This object contains information about the message representation for Google Cloud Pub/Sub.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding." + }, + "attributes": { + "type": "object" + }, + "orderingKey": { + "type": "string" + }, + "schema": { + "type": "object", + "additionalItems": false, + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "examples": [ + { + "schema": { + "name": "projects/your-project-id/schemas/your-avro-schema-id" + } + }, + { + "schema": { + "name": "projects/your-project-id/schemas/your-protobuf-schema-id" + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/messageTrait.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/messageTrait.json", + "type": "object", + "description": "Describes a trait that MAY be applied to a Message Object. This object MAY contain any property from the Message Object, except payload and traits.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "contentType": { + "type": "string", + "description": "The content type to use when encoding/decoding a message's payload. The value MUST be a specific media type (e.g. application/json). When omitted, the value MUST be the one specified on the defaultContentType field." + }, + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/anySchema.json" + }, + "correlationId": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/correlationId.json" + } + ] + }, + "tags": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the message." + }, + "name": { + "type": "string", + "description": "Name of the message." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the message." + }, + "description": { + "type": "string", + "description": "A longer description of the message. CommonMark is allowed." + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "description": "List of examples.", + "items": { + "type": "object" + } + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "contentType": "application/json" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/parameters.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/parameters.json", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/parameter.json" + } + ] + }, + "description": "JSON objects describing re-usable channel parameters.", + "examples": [ + { + "address": "user/{userId}/signedup", + "parameters": { + "userId": { + "description": "Id of the user." + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/parameter.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/parameter.json", + "description": "Describes a parameter included in a channel address.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "enum": { + "description": "An enumeration of string values to be used if the substitution options are from a limited set.", + "type": "array", + "items": { + "type": "string" + } + }, + "default": { + "description": "The default value to use for substitution, and to send, if an alternate value is not supplied.", + "type": "string" + }, + "examples": { + "description": "An array of examples of the parameter value.", + "type": "array", + "items": { + "type": "string" + } + }, + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the parameter value", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + } + }, + "examples": [ + { + "address": "user/{userId}/signedup", + "parameters": { + "userId": { + "description": "Id of the user.", + "location": "$message.payload#/user/id" + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/channelBindingsObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/channelBindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for a channel.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "http": {}, + "ws": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/websockets/0.1.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/websockets/0.1.0/channel.json" + } + } + ] + }, + "amqp": { + "properties": { + "bindingVersion": { + "enum": [ + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/channel.json" + } + } + ] + }, + "amqp1": {}, + "mqtt": {}, + "kafka": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.3.0/channel.json" + } + } + ] + }, + "anypointmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/anypointmq/0.0.1/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/anypointmq/0.0.1/channel.json" + } + } + ] + }, + "nats": {}, + "jms": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/channel.json" + } + } + ] + }, + "sns": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json" + } + } + ] + }, + "sqs": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json" + } + } + ] + }, + "stomp": {}, + "redis": {}, + "ibmmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/channel.json" + } + } + ] + }, + "solace": {}, + "googlepubsub": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/googlepubsub/0.2.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/googlepubsub/0.2.0/channel.json" + } + } + ] + }, + "pulsar": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/pulsar/0.1.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/pulsar/0.1.0/channel.json" + } + } + ] + } + } + }, + "http://asyncapi.com/bindings/websockets/0.1.0/channel.json": { + "$id": "http://asyncapi.com/bindings/websockets/0.1.0/channel.json", + "title": "WebSockets channel bindings object", + "description": "When using WebSockets, the channel represents the connection. Unlike other protocols that support multiple virtual channels (topics, routing keys, etc.) per connection, WebSockets doesn't support virtual channels or, put it another way, there's only one channel and its characteristics are strongly related to the protocol used for the handshake, i.e., HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "POST" + ], + "description": "The HTTP method to use when establishing the connection. Its value MUST be either 'GET' or 'POST'." + }, + "query": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "A Schema object containing the definitions for each query parameter. This schema MUST be of type 'object' and have a 'properties' key." + }, + "headers": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "A Schema object containing the definitions of the HTTP headers to use when establishing the connection. This schema MUST be of type 'object' and have a 'properties' key." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "method": "POST", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/amqp/0.3.0/channel.json": { + "$id": "http://asyncapi.com/bindings/amqp/0.3.0/channel.json", + "title": "AMQP channel bindings object", + "description": "This object contains information about the channel representation in AMQP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "is": { + "type": "string", + "enum": [ + "queue", + "routingKey" + ], + "description": "Defines what type of channel is it. Can be either 'queue' or 'routingKey' (default)." + }, + "exchange": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the exchange. It MUST NOT exceed 255 characters long." + }, + "type": { + "type": "string", + "enum": [ + "topic", + "direct", + "fanout", + "default", + "headers" + ], + "description": "The type of the exchange. Can be either 'topic', 'direct', 'fanout', 'default' or 'headers'." + }, + "durable": { + "type": "boolean", + "description": "Whether the exchange should survive broker restarts or not." + }, + "autoDelete": { + "type": "boolean", + "description": "Whether the exchange should be deleted when the last queue is unbound from it." + }, + "vhost": { + "type": "string", + "default": "/", + "description": "The virtual host of the exchange. Defaults to '/'." + } + }, + "description": "When is=routingKey, this object defines the exchange properties." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the queue. It MUST NOT exceed 255 characters long." + }, + "durable": { + "type": "boolean", + "description": "Whether the queue should survive broker restarts or not." + }, + "exclusive": { + "type": "boolean", + "description": "Whether the queue should be used only by one connection or not." + }, + "autoDelete": { + "type": "boolean", + "description": "Whether the queue should be deleted when the last consumer unsubscribes." + }, + "vhost": { + "type": "string", + "default": "/", + "description": "The virtual host of the queue. Defaults to '/'." + } + }, + "description": "When is=queue, this object defines the queue properties." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "oneOf": [ + { + "properties": { + "is": { + "const": "routingKey" + } + }, + "required": [ + "exchange" + ], + "not": { + "required": [ + "queue" + ] + } + }, + { + "properties": { + "is": { + "const": "queue" + } + }, + "required": [ + "queue" + ], + "not": { + "required": [ + "exchange" + ] + } + } + ], + "examples": [ + { + "is": "routingKey", + "exchange": { + "name": "myExchange", + "type": "topic", + "durable": true, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + }, + { + "is": "queue", + "queue": { + "name": "my-queue-name", + "durable": true, + "exclusive": true, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.4.0/channel.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.4.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "topic": { + "type": "string", + "description": "Kafka topic name if different from channel name." + }, + "partitions": { + "type": "integer", + "minimum": 1, + "description": "Number of partitions configured on this topic." + }, + "replicas": { + "type": "integer", + "minimum": 1, + "description": "Number of replicas configured on this topic." + }, + "topicConfiguration": { + "description": "Topic configuration properties that are relevant for the API.", + "type": "object", + "additionalProperties": false, + "properties": { + "cleanup.policy": { + "description": "The [`cleanup.policy`](https://kafka.apache.org/documentation/#topicconfigs_cleanup.policy) configuration option.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "compact", + "delete" + ] + } + }, + "retention.ms": { + "description": "The [`retention.ms`](https://kafka.apache.org/documentation/#topicconfigs_retention.ms) configuration option.", + "type": "integer", + "minimum": -1 + }, + "retention.bytes": { + "description": "The [`retention.bytes`](https://kafka.apache.org/documentation/#topicconfigs_retention.bytes) configuration option.", + "type": "integer", + "minimum": -1 + }, + "delete.retention.ms": { + "description": "The [`delete.retention.ms`](https://kafka.apache.org/documentation/#topicconfigs_delete.retention.ms) configuration option.", + "type": "integer", + "minimum": 0 + }, + "max.message.bytes": { + "description": "The [`max.message.bytes`](https://kafka.apache.org/documentation/#topicconfigs_max.message.bytes) configuration option.", + "type": "integer", + "minimum": 0 + } + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "topic": "my-specific-topic", + "partitions": 20, + "replicas": 3, + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.3.0/channel.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.3.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "topic": { + "type": "string", + "description": "Kafka topic name if different from channel name." + }, + "partitions": { + "type": "integer", + "minimum": 1, + "description": "Number of partitions configured on this topic." + }, + "replicas": { + "type": "integer", + "minimum": 1, + "description": "Number of replicas configured on this topic." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "topic": "my-specific-topic", + "partitions": 20, + "replicas": 3, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/anypointmq/0.0.1/channel.json": { + "$id": "http://asyncapi.com/bindings/anypointmq/0.0.1/channel.json", + "title": "Anypoint MQ channel bindings object", + "description": "This object contains configuration for describing an Anypoint MQ exchange, queue, or FIFO queue as an AsyncAPI channel. This objects only contains configuration that can not be provided in the AsyncAPI standard channel object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "destination": { + "type": "string", + "description": "The destination (queue or exchange) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name in Anypoint MQ. Defaults to the channel name." + }, + "destinationType": { + "type": "string", + "enum": [ + "exchange", + "queue", + "fifo-queue" + ], + "default": "queue", + "description": "The type of destination. SHOULD be specified to document the messaging model (publish/subscribe, point-to-point, strict message ordering) supported by this channel." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "destination": "user-signup-exchg", + "destinationType": "exchange", + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/jms/0.0.1/channel.json": { + "$id": "http://asyncapi.com/bindings/jms/0.0.1/channel.json", + "title": "Channel Schema", + "description": "This object contains configuration for describing a JMS queue, or FIFO queue as an AsyncAPI channel. This objects only contains configuration that can not be provided in the AsyncAPI standard channel object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "destination": { + "type": "string", + "description": "The destination (queue) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name according to the JMS Provider. Defaults to the channel name." + }, + "destinationType": { + "type": "string", + "enum": [ + "queue", + "fifo-queue" + ], + "default": "queue", + "description": "The type of destination. SHOULD be specified to document the messaging model (point-to-point, or strict message ordering) supported by this channel." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "destination": "user-signed-up", + "destinationType": "fifo-queue", + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/sns/0.1.0/channel.json": { + "$id": "http://asyncapi.com/bindings/sns/0.1.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in SNS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "name": { + "type": "string", + "description": "The name of the topic. Can be different from the channel name to allow flexibility around AWS resource naming limitations." + }, + "ordering": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json#/definitions/ordering" + }, + "policy": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the topic." + }, + "bindingVersion": { + "type": "string", + "description": "The version of this binding.", + "default": "latest" + } + }, + "required": [ + "name" + ], + "definitions": { + "ordering": { + "type": "object", + "description": "By default, we assume an unordered SNS topic. This field allows configuration of a FIFO SNS Topic.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "type": { + "type": "string", + "description": "Defines the type of SNS Topic.", + "enum": [ + "standard", + "FIFO" + ] + }, + "contentBasedDeduplication": { + "type": "boolean", + "description": "True to turn on de-duplication of messages for a channel." + } + }, + "required": [ + "type" + ] + }, + "policy": { + "type": "object", + "description": "The security policy for the SNS Topic.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this topic", + "items": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SNS permission being allowed or denied e.g. sns:Publish", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "name": "my-sns-topic", + "policy": { + "statements": [ + { + "effect": "Allow", + "principal": "*", + "action": "SNS:Publish" + } + ] + } + } + ] + }, + "http://asyncapi.com/bindings/sqs/0.2.0/channel.json": { + "$id": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in SQS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "queue": { + "description": "A definition of the queue that will be used as the channel.", + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/queue" + }, + "deadLetterQueue": { + "description": "A definition of the queue that will be used for un-processable messages.", + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/queue" + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0", + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed.", + "default": "latest" + } + }, + "required": [ + "queue" + ], + "definitions": { + "queue": { + "type": "object", + "description": "A definition of a queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "name": { + "type": "string", + "description": "The name of the queue. When an SNS Operation Binding Object references an SQS queue by name, the identifier should be the one in this field." + }, + "fifoQueue": { + "type": "boolean", + "description": "Is this a FIFO queue?", + "default": false + }, + "deduplicationScope": { + "type": "string", + "enum": [ + "queue", + "messageGroup" + ], + "description": "Specifies whether message deduplication occurs at the message group or queue level. Valid values are messageGroup and queue (default).", + "default": "queue" + }, + "fifoThroughputLimit": { + "type": "string", + "enum": [ + "perQueue", + "perMessageGroupId" + ], + "description": "Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group. Valid values are perQueue (default) and perMessageGroupId.", + "default": "perQueue" + }, + "deliveryDelay": { + "type": "integer", + "description": "The number of seconds to delay before a message sent to the queue can be received. used to create a delay queue.", + "minimum": 0, + "maximum": 15, + "default": 0 + }, + "visibilityTimeout": { + "type": "integer", + "description": "The length of time, in seconds, that a consumer locks a message - hiding it from reads - before it is unlocked and can be read again.", + "minimum": 0, + "maximum": 43200, + "default": 30 + }, + "receiveMessageWaitTime": { + "type": "integer", + "description": "Determines if the queue uses short polling or long polling. Set to zero the queue reads available messages and returns immediately. Set to a non-zero integer, long polling waits the specified number of seconds for messages to arrive before returning.", + "default": 0 + }, + "messageRetentionPeriod": { + "type": "integer", + "description": "How long to retain a message on the queue in seconds, unless deleted.", + "minimum": 60, + "maximum": 1209600, + "default": 345600 + }, + "redrivePolicy": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/redrivePolicy" + }, + "policy": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the queue." + } + }, + "required": [ + "name", + "fifoQueue" + ] + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/identifier" + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + }, + "identifier": { + "type": "object", + "description": "The SQS queue to use as a dead letter queue (DLQ).", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding." + } + } + }, + "policy": { + "type": "object", + "description": "The security policy for the SQS Queue", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this queue.", + "items": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SQS permission being allowed or denied e.g. sqs:ReceiveMessage", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "queue": { + "name": "myQueue", + "fifoQueue": true, + "deduplicationScope": "messageGroup", + "fifoThroughputLimit": "perMessageGroupId", + "deliveryDelay": 15, + "visibilityTimeout": 60, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 86400, + "redrivePolicy": { + "deadLetterQueue": { + "arn": "arn:aws:SQS:eu-west-1:0000000:123456789" + }, + "maxReceiveCount": 15 + }, + "policy": { + "statements": [ + { + "effect": "Deny", + "principal": "arn:aws:iam::123456789012:user/dec.kolakowski", + "action": [ + "sqs:SendMessage", + "sqs:ReceiveMessage" + ] + } + ] + }, + "tags": { + "owner": "AsyncAPI.NET", + "platform": "AsyncAPIOrg" + } + }, + "deadLetterQueue": { + "name": "myQueue_error", + "deliveryDelay": 0, + "visibilityTimeout": 0, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 604800 + } + } + ] + }, + "http://asyncapi.com/bindings/ibmmq/0.1.0/channel.json": { + "$id": "http://asyncapi.com/bindings/ibmmq/0.1.0/channel.json", + "title": "IBM MQ channel bindings object", + "description": "This object contains information about the channel representation in IBM MQ. Each channel corresponds to a Queue or Topic within IBM MQ.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "destinationType": { + "type": "string", + "enum": [ + "topic", + "queue" + ], + "default": "topic", + "description": "Defines the type of AsyncAPI channel." + }, + "queue": { + "type": "object", + "description": "Defines the properties of a queue.", + "properties": { + "objectName": { + "type": "string", + "maxLength": 48, + "description": "Defines the name of the IBM MQ queue associated with the channel." + }, + "isPartitioned": { + "type": "boolean", + "default": false, + "description": "Defines if the queue is a cluster queue and therefore partitioned. If 'true', a binding option MAY be specified when accessing the queue. More information on binding options can be found on this page in the IBM MQ Knowledge Center." + }, + "exclusive": { + "type": "boolean", + "default": false, + "description": "Specifies if it is recommended to open the queue exclusively." + } + }, + "required": [ + "objectName" + ] + }, + "topic": { + "type": "object", + "description": "Defines the properties of a topic.", + "properties": { + "string": { + "type": "string", + "maxLength": 10240, + "description": "The value of the IBM MQ topic string to be used." + }, + "objectName": { + "type": "string", + "maxLength": 48, + "description": "The name of the IBM MQ topic object." + }, + "durablePermitted": { + "type": "boolean", + "default": true, + "description": "Defines if the subscription may be durable." + }, + "lastMsgRetained": { + "type": "boolean", + "default": false, + "description": "Defines if the last message published will be made available to new subscriptions." + } + } + }, + "maxMsgLength": { + "type": "integer", + "minimum": 0, + "maximum": 104857600, + "description": "The maximum length of the physical message (in bytes) accepted by the Topic or Queue. Messages produced that are greater in size than this value may fail to be delivered. More information on the maximum message length can be found on this [page](https://www.ibm.com/support/knowledgecenter/SSFKSJ_latest/com.ibm.mq.ref.dev.doc/q097520_.html) in the IBM MQ Knowledge Center." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "const": "topic" + } + }, + "not": { + "required": [ + "queue" + ] + } + }, + { + "properties": { + "destinationType": { + "const": "queue" + } + }, + "required": [ + "queue" + ], + "not": { + "required": [ + "topic" + ] + } + } + ], + "examples": [ + { + "destinationType": "topic", + "topic": { + "objectName": "myTopicName" + }, + "bindingVersion": "0.1.0" + }, + { + "destinationType": "queue", + "queue": { + "objectName": "myQueueName", + "exclusive": true + }, + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/googlepubsub/0.2.0/channel.json": { + "$id": "http://asyncapi.com/bindings/googlepubsub/0.2.0/channel.json", + "title": "Cloud Pub/Sub Channel Schema", + "description": "This object contains information about the channel representation for Google Cloud Pub/Sub.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding." + }, + "labels": { + "type": "object" + }, + "messageRetentionDuration": { + "type": "string" + }, + "messageStoragePolicy": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowedPersistenceRegions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schemaSettings": { + "type": "object", + "additionalItems": false, + "properties": { + "encoding": { + "type": "string" + }, + "firstRevisionId": { + "type": "string" + }, + "lastRevisionId": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "encoding", + "name" + ] + } + }, + "required": [ + "schemaSettings" + ], + "examples": [ + { + "labels": { + "label1": "value1", + "label2": "value2" + }, + "messageRetentionDuration": "86400s", + "messageStoragePolicy": { + "allowedPersistenceRegions": [ + "us-central1", + "us-east1" + ] + }, + "schemaSettings": { + "encoding": "json", + "name": "projects/your-project-id/schemas/your-schema" + } + } + ] + }, + "http://asyncapi.com/bindings/pulsar/0.1.0/channel.json": { + "$id": "http://asyncapi.com/bindings/pulsar/0.1.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in Pulsar, which covers namespace and topic level admin configuration. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "required": [ + "namespace", + "persistence" + ], + "properties": { + "namespace": { + "type": "string", + "description": "The namespace, the channel is associated with." + }, + "persistence": { + "type": "string", + "enum": [ + "persistent", + "non-persistent" + ], + "description": "persistence of the topic in Pulsar." + }, + "compaction": { + "type": "integer", + "minimum": 0, + "description": "Topic compaction threshold given in MB" + }, + "geo-replication": { + "type": "array", + "description": "A list of clusters the topic is replicated to.", + "items": { + "type": "string" + } + }, + "retention": { + "type": "object", + "additionalProperties": false, + "properties": { + "time": { + "type": "integer", + "minimum": 0, + "description": "Time given in Minutes. `0` = Disable message retention." + }, + "size": { + "type": "integer", + "minimum": 0, + "description": "Size given in MegaBytes. `0` = Disable message retention." + } + } + }, + "ttl": { + "type": "integer", + "description": "TTL in seconds for the specified topic" + }, + "deduplication": { + "type": "boolean", + "description": "Whether deduplication of events is enabled or not." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "namespace": "ns1", + "persistence": "persistent", + "compaction": 1000, + "retention": { + "time": 15, + "size": 1000 + }, + "ttl": 360, + "geo-replication": [ + "us-west", + "us-east" + ], + "deduplication": true, + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operations.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operations.json", + "type": "object", + "description": "Holds a dictionary with all the operations this application MUST implement.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json" + } + ] + }, + "examples": [ + { + "onUserSignUp": { + "title": "User sign up", + "summary": "Action to sign a user up.", + "description": "A longer description", + "channel": { + "$ref": "#/channels/userSignup" + }, + "action": "send", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "bindings": { + "amqp": { + "ack": false + } + }, + "traits": [ + { + "$ref": "#/components/operationTraits/kafka" + } + ] + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operation.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operation.json", + "type": "object", + "description": "Describes a specific operation.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "required": [ + "action", + "channel" + ], + "properties": { + "action": { + "type": "string", + "description": "Allowed values are send and receive. Use send when it's expected that the application will send a message to the given channel, and receive when the application should expect receiving messages from the given channel.", + "enum": [ + "send", + "receive" + ] + }, + "channel": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + "messages": { + "type": "array", + "description": "A list of $ref pointers pointing to the supported Message Objects that can be processed by this operation. It MUST contain a subset of the messages defined in the channel referenced in this operation. Every message processed by this operation MUST be valid against one, and only one, of the message objects referenced in this list. Please note the messages property value MUST be a list of Reference Objects and, therefore, MUST NOT contain Message Objects. However, it is RECOMMENDED that parsers (or other software) dereference this property for a better development experience.", + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + }, + "reply": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationReply.json" + } + ] + }, + "traits": { + "type": "array", + "description": "A list of traits to apply to the operation object. Traits MUST be merged using traits merge mechanism. The resulting object MUST be a valid Operation Object.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationTrait.json" + } + ] + } + }, + "title": { + "type": "string", + "description": "A human-friendly title for the operation." + }, + "summary": { + "type": "string", + "description": "A brief summary of the operation." + }, + "description": { + "type": "string", + "description": "A longer description of the operation. CommonMark is allowed." + }, + "security": { + "$ref": "http://asyncapi.com/definitions/3.0.0/securityRequirements.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of operations.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "title": "User sign up", + "summary": "Action to sign a user up.", + "description": "A longer description", + "channel": { + "$ref": "#/channels/userSignup" + }, + "action": "send", + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "bindings": { + "amqp": { + "ack": false + } + }, + "traits": [ + { + "$ref": "#/components/operationTraits/kafka" + } + ], + "messages": [ + { + "$ref": "/components/messages/userSignedUp" + } + ], + "reply": { + "address": { + "location": "$message.header#/replyTo" + }, + "channel": { + "$ref": "#/channels/userSignupReply" + }, + "messages": [ + { + "$ref": "/components/messages/userSignedUpReply" + } + ] + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operationReply.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operationReply.json", + "type": "object", + "description": "Describes the reply part that MAY be applied to an Operation Object. If an operation implements the request/reply pattern, the reply object represents the response message.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "address": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationReplyAddress.json" + } + ] + }, + "channel": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + "messages": { + "type": "array", + "description": "A list of $ref pointers pointing to the supported Message Objects that can be processed by this operation as reply. It MUST contain a subset of the messages defined in the channel referenced in this operation reply. Every message processed by this operation MUST be valid against one, and only one, of the message objects referenced in this list. Please note the messages property value MUST be a list of Reference Objects and, therefore, MUST NOT contain Message Objects. However, it is RECOMMENDED that parsers (or other software) dereference this property for a better development experience.", + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + } + } + }, + "http://asyncapi.com/definitions/3.0.0/operationReplyAddress.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operationReplyAddress.json", + "type": "object", + "description": "An object that specifies where an operation has to send the reply", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "required": [ + "location" + ], + "properties": { + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the reply address.", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + }, + "description": { + "type": "string", + "description": "An optional description of the address. CommonMark is allowed." + } + }, + "examples": [ + { + "description": "Consumer inbox", + "location": "$message.header#/replyTo" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operationTrait.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operationTrait.json", + "type": "object", + "description": "Describes a trait that MAY be applied to an Operation Object. This object MAY contain any property from the Operation Object, except the action, channel and traits ones.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "title": { + "description": "A human-friendly title for the operation.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/title" + }, + "summary": { + "description": "A short summary of what the operation is about.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/summary" + }, + "description": { + "description": "A verbose explanation of the operation. CommonMark syntax can be used for rich text representation.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/description" + }, + "security": { + "description": "A declaration of which security schemes are associated with this operation. Only one of the security scheme objects MUST be satisfied to authorize an operation. In cases where Server Security also applies, it MUST also be satisfied.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/security" + }, + "tags": { + "description": "A list of tags for logical grouping and categorization of operations.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/tags" + }, + "externalDocs": { + "description": "Additional external documentation for this operation.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/externalDocs" + }, + "bindings": { + "description": "A map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the operation.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "bindings": { + "amqp": { + "ack": false + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for an operation.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "http": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.2.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.2.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.3.0/operation.json" + } + } + ] + }, + "ws": {}, + "amqp": { + "properties": { + "bindingVersion": { + "enum": [ + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/operation.json" + } + } + ] + }, + "amqp1": {}, + "mqtt": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/operation.json" + } + } + ] + }, + "kafka": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.3.0/operation.json" + } + } + ] + }, + "anypointmq": {}, + "nats": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/nats/0.1.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/nats/0.1.0/operation.json" + } + } + ] + }, + "jms": {}, + "sns": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json" + } + } + ] + }, + "sqs": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json" + } + } + ] + }, + "stomp": {}, + "redis": {}, + "ibmmq": {}, + "solace": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0", + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.4.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.4.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.3.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.2.0/operation.json" + } + } + ] + }, + "googlepubsub": {} + } + }, + "http://asyncapi.com/bindings/http/0.2.0/operation.json": { + "$id": "http://asyncapi.com/bindings/http/0.2.0/operation.json", + "title": "HTTP operation bindings object", + "description": "This object contains information about the operation representation in HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "PATCH", + "DELETE", + "HEAD", + "OPTIONS", + "CONNECT", + "TRACE" + ], + "description": "When 'type' is 'request', this is the HTTP method, otherwise it MUST be ignored. Its value MUST be one of 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', and 'TRACE'." + }, + "query": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "A Schema object containing the definitions for each query parameter. This schema MUST be of type 'object' and have a properties key." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "query": { + "type": "object", + "required": [ + "companyId" + ], + "properties": { + "companyId": { + "type": "number", + "minimum": 1, + "description": "The Id of the company." + } + }, + "additionalProperties": false + }, + "bindingVersion": "0.2.0" + }, + { + "method": "GET", + "query": { + "type": "object", + "required": [ + "companyId" + ], + "properties": { + "companyId": { + "type": "number", + "minimum": 1, + "description": "The Id of the company." + } + }, + "additionalProperties": false + }, + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/http/0.3.0/operation.json": { + "$id": "http://asyncapi.com/bindings/http/0.3.0/operation.json", + "title": "HTTP operation bindings object", + "description": "This object contains information about the operation representation in HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "PATCH", + "DELETE", + "HEAD", + "OPTIONS", + "CONNECT", + "TRACE" + ], + "description": "When 'type' is 'request', this is the HTTP method, otherwise it MUST be ignored. Its value MUST be one of 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', and 'TRACE'." + }, + "query": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "A Schema object containing the definitions for each query parameter. This schema MUST be of type 'object' and have a properties key." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "query": { + "type": "object", + "required": [ + "companyId" + ], + "properties": { + "companyId": { + "type": "number", + "minimum": 1, + "description": "The Id of the company." + } + }, + "additionalProperties": false + }, + "bindingVersion": "0.3.0" + }, + { + "method": "GET", + "query": { + "type": "object", + "required": [ + "companyId" + ], + "properties": { + "companyId": { + "type": "number", + "minimum": 1, + "description": "The Id of the company." + } + }, + "additionalProperties": false + }, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/amqp/0.3.0/operation.json": { + "$id": "http://asyncapi.com/bindings/amqp/0.3.0/operation.json", + "title": "AMQP operation bindings object", + "description": "This object contains information about the operation representation in AMQP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "expiration": { + "type": "integer", + "minimum": 0, + "description": "TTL (Time-To-Live) for the message. It MUST be greater than or equal to zero." + }, + "userId": { + "type": "string", + "description": "Identifies the user who has sent the message." + }, + "cc": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The routing keys the message should be routed to at the time of publishing." + }, + "priority": { + "type": "integer", + "description": "A priority for the message." + }, + "deliveryMode": { + "type": "integer", + "enum": [ + 1, + 2 + ], + "description": "Delivery mode of the message. Its value MUST be either 1 (transient) or 2 (persistent)." + }, + "mandatory": { + "type": "boolean", + "description": "Whether the message is mandatory or not." + }, + "bcc": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Like cc but consumers will not receive this information." + }, + "timestamp": { + "type": "boolean", + "description": "Whether the message should include a timestamp or not." + }, + "ack": { + "type": "boolean", + "description": "Whether the consumer should ack the message or not." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "expiration": 100000, + "userId": "guest", + "cc": [ + "user.logs" + ], + "priority": 10, + "deliveryMode": 2, + "mandatory": false, + "bcc": [ + "external.audit" + ], + "timestamp": true, + "ack": false, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/mqtt/0.2.0/operation.json": { + "$id": "http://asyncapi.com/bindings/mqtt/0.2.0/operation.json", + "title": "MQTT operation bindings object", + "description": "This object contains information about the operation representation in MQTT.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "qos": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "Defines the Quality of Service (QoS) levels for the message flow between client and server. Its value MUST be either 0 (At most once delivery), 1 (At least once delivery), or 2 (Exactly once delivery)." + }, + "retain": { + "type": "boolean", + "description": "Whether the broker should retain the message or not." + }, + "messageExpiryInterval": { + "oneOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "Lifetime of the message in seconds" + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "qos": 2, + "retain": true, + "messageExpiryInterval": 60, + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.4.0/operation.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.4.0/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "groupId": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "Id of the consumer group." + }, + "clientId": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "Id of the consumer inside a consumer group." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "groupId": { + "type": "string", + "enum": [ + "myGroupId" + ] + }, + "clientId": { + "type": "string", + "enum": [ + "myClientId" + ] + }, + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.3.0/operation.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.3.0/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "groupId": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "Id of the consumer group." + }, + "clientId": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "Id of the consumer inside a consumer group." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "groupId": { + "type": "string", + "enum": [ + "myGroupId" + ] + }, + "clientId": { + "type": "string", + "enum": [ + "myClientId" + ] + }, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/nats/0.1.0/operation.json": { + "$id": "http://asyncapi.com/bindings/nats/0.1.0/operation.json", + "title": "NATS operation bindings object", + "description": "This object contains information about the operation representation in NATS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "queue": { + "type": "string", + "description": "Defines the name of the queue to use. It MUST NOT exceed 255 characters.", + "maxLength": 255 + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "queue": "MyCustomQueue", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/sns/0.1.0/operation.json": { + "$id": "http://asyncapi.com/bindings/sns/0.1.0/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in SNS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "topic": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/identifier", + "description": "Often we can assume that the SNS Topic is the channel name-we provide this field in case the you need to supply the ARN, or the Topic name is not the channel name in the AsyncAPI document." + }, + "consumers": { + "type": "array", + "description": "The protocols that listen to this topic and their endpoints.", + "items": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/consumer" + }, + "minItems": 1 + }, + "deliveryPolicy": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/deliveryPolicy", + "description": "Policy for retries to HTTP. The field is the default for HTTP receivers of the SNS Topic which may be overridden by a specific consumer." + }, + "bindingVersion": { + "type": "string", + "description": "The version of this binding.", + "default": "latest" + } + }, + "required": [ + "consumers" + ], + "definitions": { + "identifier": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "url": { + "type": "string", + "description": "The endpoint is a URL." + }, + "email": { + "type": "string", + "description": "The endpoint is an email address." + }, + "phone": { + "type": "string", + "description": "The endpoint is a phone number." + }, + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding. We don't use $ref because we are referring, not including." + } + } + }, + "consumer": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "protocol": { + "description": "The protocol that this endpoint receives messages by.", + "type": "string", + "enum": [ + "http", + "https", + "email", + "email-json", + "sms", + "sqs", + "application", + "lambda", + "firehose" + ] + }, + "endpoint": { + "description": "The endpoint messages are delivered to.", + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/identifier" + }, + "filterPolicy": { + "type": "object", + "description": "Only receive a subset of messages from the channel, determined by this policy. Depending on the FilterPolicyScope, a map of either a message attribute or message body to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + } + }, + "filterPolicyScope": { + "type": "string", + "description": "Determines whether the FilterPolicy applies to MessageAttributes or MessageBody.", + "enum": [ + "MessageAttributes", + "MessageBody" + ], + "default": "MessageAttributes" + }, + "rawMessageDelivery": { + "type": "boolean", + "description": "If true AWS SNS attributes are removed from the body, and for SQS, SNS message attributes are copied to SQS message attributes. If false the SNS attributes are included in the body." + }, + "redrivePolicy": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/redrivePolicy" + }, + "deliveryPolicy": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/deliveryPolicy", + "description": "Policy for retries to HTTP. The parameter is for that SNS Subscription and overrides any policy on the SNS Topic." + }, + "displayName": { + "type": "string", + "description": "The display name to use with an SNS subscription" + } + }, + "required": [ + "protocol", + "endpoint", + "rawMessageDelivery" + ] + }, + "deliveryPolicy": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "minDelayTarget": { + "type": "integer", + "description": "The minimum delay for a retry in seconds." + }, + "maxDelayTarget": { + "type": "integer", + "description": "The maximum delay for a retry in seconds." + }, + "numRetries": { + "type": "integer", + "description": "The total number of retries, including immediate, pre-backoff, backoff, and post-backoff retries." + }, + "numNoDelayRetries": { + "type": "integer", + "description": "The number of immediate retries (with no delay)." + }, + "numMinDelayRetries": { + "type": "integer", + "description": "The number of immediate retries (with delay)." + }, + "numMaxDelayRetries": { + "type": "integer", + "description": "The number of post-backoff phase retries, with the maximum delay between retries." + }, + "backoffFunction": { + "type": "string", + "description": "The algorithm for backoff between retries.", + "enum": [ + "arithmetic", + "exponential", + "geometric", + "linear" + ] + }, + "maxReceivesPerSecond": { + "type": "integer", + "description": "The maximum number of deliveries per second, per subscription." + } + } + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/identifier", + "description": "The SQS queue to use as a dead letter queue (DLQ)." + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + } + }, + "examples": [ + { + "topic": { + "name": "someTopic" + }, + "consumers": [ + { + "protocol": "sqs", + "endpoint": { + "name": "someQueue" + }, + "filterPolicy": { + "store": [ + "asyncapi_corp" + ], + "event": [ + { + "anything-but": "order_cancelled" + } + ], + "customer_interests": [ + "rugby", + "football", + "baseball" + ] + }, + "filterPolicyScope": "MessageAttributes", + "rawMessageDelivery": false, + "redrivePolicy": { + "deadLetterQueue": { + "arn": "arn:aws:SQS:eu-west-1:0000000:123456789" + }, + "maxReceiveCount": 25 + }, + "deliveryPolicy": { + "minDelayTarget": 10, + "maxDelayTarget": 100, + "numRetries": 5, + "numNoDelayRetries": 2, + "numMinDelayRetries": 3, + "numMaxDelayRetries": 5, + "backoffFunction": "linear", + "maxReceivesPerSecond": 2 + } + } + ] + } + ] + }, + "http://asyncapi.com/bindings/sqs/0.2.0/operation.json": { + "$id": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in SQS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "queues": { + "type": "array", + "description": "Queue objects that are either the endpoint for an SNS Operation Binding Object, or the deadLetterQueue of the SQS Operation Binding Object.", + "items": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/queue" + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0", + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed.", + "default": "latest" + } + }, + "required": [ + "queues" + ], + "definitions": { + "queue": { + "type": "object", + "description": "A definition of a queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "$ref": { + "type": "string", + "description": "Allows for an external definition of a queue. The referenced structure MUST be in the format of a Queue. If there are conflicts between the referenced definition and this Queue's definition, the behavior is undefined." + }, + "name": { + "type": "string", + "description": "The name of the queue. When an SNS Operation Binding Object references an SQS queue by name, the identifier should be the one in this field." + }, + "fifoQueue": { + "type": "boolean", + "description": "Is this a FIFO queue?", + "default": false + }, + "deduplicationScope": { + "type": "string", + "enum": [ + "queue", + "messageGroup" + ], + "description": "Specifies whether message deduplication occurs at the message group or queue level. Valid values are messageGroup and queue (default).", + "default": "queue" + }, + "fifoThroughputLimit": { + "type": "string", + "enum": [ + "perQueue", + "perMessageGroupId" + ], + "description": "Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group. Valid values are perQueue (default) and perMessageGroupId.", + "default": "perQueue" + }, + "deliveryDelay": { + "type": "integer", + "description": "The number of seconds to delay before a message sent to the queue can be received. Used to create a delay queue.", + "minimum": 0, + "maximum": 15, + "default": 0 + }, + "visibilityTimeout": { + "type": "integer", + "description": "The length of time, in seconds, that a consumer locks a message - hiding it from reads - before it is unlocked and can be read again.", + "minimum": 0, + "maximum": 43200, + "default": 30 + }, + "receiveMessageWaitTime": { + "type": "integer", + "description": "Determines if the queue uses short polling or long polling. Set to zero the queue reads available messages and returns immediately. Set to a non-zero integer, long polling waits the specified number of seconds for messages to arrive before returning.", + "default": 0 + }, + "messageRetentionPeriod": { + "type": "integer", + "description": "How long to retain a message on the queue in seconds, unless deleted.", + "minimum": 60, + "maximum": 1209600, + "default": 345600 + }, + "redrivePolicy": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/redrivePolicy" + }, + "policy": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the queue." + } + }, + "required": [ + "name" + ] + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/identifier" + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + }, + "identifier": { + "type": "object", + "description": "The SQS queue to use as a dead letter queue (DLQ).", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding." + } + } + }, + "policy": { + "type": "object", + "description": "The security policy for the SQS Queue", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this queue.", + "items": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SQS permission being allowed or denied e.g. sqs:ReceiveMessage", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "queues": [ + { + "name": "myQueue", + "fifoQueue": true, + "deduplicationScope": "messageGroup", + "fifoThroughputLimit": "perMessageGroupId", + "deliveryDelay": 10, + "redrivePolicy": { + "deadLetterQueue": { + "name": "myQueue_error" + }, + "maxReceiveCount": 15 + }, + "policy": { + "statements": [ + { + "effect": "Deny", + "principal": "arn:aws:iam::123456789012:user/dec.kolakowski", + "action": [ + "sqs:SendMessage", + "sqs:ReceiveMessage" + ] + } + ] + } + }, + { + "name": "myQueue_error", + "deliveryDelay": 10 + } + ] + } + ] + }, + "http://asyncapi.com/bindings/solace/0.4.0/operation.json": { + "$id": "http://asyncapi.com/bindings/solace/0.4.0/operation.json", + "title": "Solace operation bindings object", + "description": "This object contains information about the operation representation in Solace.", + "type": "object", + "additionalProperties": false, + "properties": { + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + }, + "destinations": { + "description": "The list of Solace destinations referenced in the operation.", + "type": "array", + "items": { + "type": "object", + "properties": { + "deliveryMode": { + "type": "string", + "enum": [ + "direct", + "persistent" + ] + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "type": "string", + "const": "queue", + "description": "If the type is queue, then the subscriber can bind to the queue. The queue subscribes to the given topicSubscriptions. If no topicSubscriptions are provied, the queue will subscribe to the topic as represented by the channel name." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the queue" + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the queue subscribes to.", + "items": { + "type": "string" + } + }, + "accessType": { + "type": "string", + "enum": [ + "exclusive", + "nonexclusive" + ] + }, + "maxTtl": { + "type": "string", + "description": "The maximum TTL to apply to messages to be spooled." + }, + "maxMsgSpoolUsage": { + "type": "string", + "description": "The maximum amount of message spool that the given queue may use" + } + } + } + } + }, + { + "properties": { + "destinationType": { + "type": "string", + "const": "topic", + "description": "If the type is topic, then the subscriber subscribes to the given topicSubscriptions. If no topicSubscriptions are provided, the client will subscribe to the topic as represented by the channel name." + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the client subscribes to.", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "timeToLive": { + "type": "integer", + "description": "Interval in milliseconds or a Schema Object containing the definition of the lifetime of the message." + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "description": "The valid priority value range is 0-255 with 0 as the lowest priority and 255 as the highest or a Schema Object containing the definition of the priority." + }, + "dmqEligible": { + "type": "boolean", + "description": "Set the message to be eligible to be moved to a Dead Message Queue. The default value is false." + } + }, + "examples": [ + { + "bindingVersion": "0.4.0", + "destinations": [ + { + "destinationType": "queue", + "queue": { + "name": "sampleQueue", + "topicSubscriptions": [ + "samples/*" + ], + "accessType": "nonexclusive" + } + }, + { + "destinationType": "topic", + "topicSubscriptions": [ + "samples/*" + ] + } + ] + } + ] + }, + "http://asyncapi.com/bindings/solace/0.3.0/operation.json": { + "$id": "http://asyncapi.com/bindings/solace/0.3.0/operation.json", + "title": "Solace operation bindings object", + "description": "This object contains information about the operation representation in Solace.", + "type": "object", + "additionalProperties": false, + "properties": { + "destinations": { + "description": "The list of Solace destinations referenced in the operation.", + "type": "array", + "items": { + "type": "object", + "properties": { + "deliveryMode": { + "type": "string", + "enum": [ + "direct", + "persistent" + ] + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "type": "string", + "const": "queue", + "description": "If the type is queue, then the subscriber can bind to the queue. The queue subscribes to the given topicSubscriptions. If no topicSubscriptions are provied, the queue will subscribe to the topic as represented by the channel name." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the queue" + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the queue subscribes to.", + "items": { + "type": "string" + } + }, + "accessType": { + "type": "string", + "enum": [ + "exclusive", + "nonexclusive" + ] + }, + "maxTtl": { + "type": "string", + "description": "The maximum TTL to apply to messages to be spooled." + }, + "maxMsgSpoolUsage": { + "type": "string", + "description": "The maximum amount of message spool that the given queue may use" + } + } + } + } + }, + { + "properties": { + "destinationType": { + "type": "string", + "const": "topic", + "description": "If the type is topic, then the subscriber subscribes to the given topicSubscriptions. If no topicSubscriptions are provided, the client will subscribe to the topic as represented by the channel name." + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the client subscribes to.", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "bindingVersion": "0.3.0", + "destinations": [ + { + "destinationType": "queue", + "queue": { + "name": "sampleQueue", + "topicSubscriptions": [ + "samples/*" + ], + "accessType": "nonexclusive" + } + }, + { + "destinationType": "topic", + "topicSubscriptions": [ + "samples/*" + ] + } + ] + } + ] + }, + "http://asyncapi.com/bindings/solace/0.2.0/operation.json": { + "$id": "http://asyncapi.com/bindings/solace/0.2.0/operation.json", + "title": "Solace operation bindings object", + "description": "This object contains information about the operation representation in Solace.", + "type": "object", + "additionalProperties": false, + "properties": { + "destinations": { + "description": "The list of Solace destinations referenced in the operation.", + "type": "array", + "items": { + "type": "object", + "properties": { + "deliveryMode": { + "type": "string", + "enum": [ + "direct", + "persistent" + ] + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "type": "string", + "const": "queue", + "description": "If the type is queue, then the subscriber can bind to the queue. The queue subscribes to the given topicSubscriptions. If no topicSubscriptions are provied, the queue will subscribe to the topic as represented by the channel name." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the queue" + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the queue subscribes to.", + "items": { + "type": "string" + } + }, + "accessType": { + "type": "string", + "enum": [ + "exclusive", + "nonexclusive" + ] + } + } + } + } + }, + { + "properties": { + "destinationType": { + "type": "string", + "const": "topic", + "description": "If the type is topic, then the subscriber subscribes to the given topicSubscriptions. If no topicSubscriptions are provided, the client will subscribe to the topic as represented by the channel name." + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the client subscribes to.", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "bindingVersion": "0.2.0", + "destinations": [ + { + "destinationType": "queue", + "queue": { + "name": "sampleQueue", + "topicSubscriptions": [ + "samples/*" + ], + "accessType": "nonexclusive" + } + }, + { + "destinationType": "topic", + "topicSubscriptions": [ + "samples/*" + ] + } + ] + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/components.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/components.json", + "type": "object", + "description": "An object to hold a set of reusable objects for different aspects of the AsyncAPI specification. All objects defined within the components object will have no effect on the API unless they are explicitly referenced from properties outside the components object.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "schemas": { + "type": "object", + "description": "An object to hold reusable Schema Object. If this is a Schema Object, then the schemaFormat will be assumed to be 'application/vnd.aai.asyncapi+json;version=asyncapi' where the version is equal to the AsyncAPI Version String.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/anySchema.json" + } + ] + } + } + }, + "servers": { + "type": "object", + "description": "An object to hold reusable Server Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/server.json" + } + ] + } + } + }, + "channels": { + "type": "object", + "description": "An object to hold reusable Channel Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/channel.json" + } + ] + } + } + }, + "serverVariables": { + "type": "object", + "description": "An object to hold reusable Server Variable Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverVariable.json" + } + ] + } + } + }, + "operations": { + "type": "object", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json" + } + ] + } + } + }, + "messages": { + "type": "object", + "description": "An object to hold reusable Message Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageObject.json" + } + ] + } + } + }, + "securitySchemes": { + "type": "object", + "description": "An object to hold reusable Security Scheme Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SecurityScheme.json" + } + ] + } + } + }, + "parameters": { + "type": "object", + "description": "An object to hold reusable Parameter Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/parameter.json" + } + ] + } + } + }, + "correlationIds": { + "type": "object", + "description": "An object to hold reusable Correlation ID Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/correlationId.json" + } + ] + } + } + }, + "operationTraits": { + "type": "object", + "description": "An object to hold reusable Operation Trait Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationTrait.json" + } + ] + } + } + }, + "messageTraits": { + "type": "object", + "description": "An object to hold reusable Message Trait Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageTrait.json" + } + ] + } + } + }, + "replies": { + "type": "object", + "description": "An object to hold reusable Operation Reply Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationReply.json" + } + ] + } + } + }, + "replyAddresses": { + "type": "object", + "description": "An object to hold reusable Operation Reply Address Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationReplyAddress.json" + } + ] + } + } + }, + "serverBindings": { + "type": "object", + "description": "An object to hold reusable Server Bindings Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverBindingsObject.json" + } + ] + } + } + }, + "channelBindings": { + "type": "object", + "description": "An object to hold reusable Channel Bindings Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/channelBindingsObject.json" + } + ] + } + } + }, + "operationBindings": { + "type": "object", + "description": "An object to hold reusable Operation Bindings Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json" + } + ] + } + } + }, + "messageBindings": { + "type": "object", + "description": "An object to hold reusable Message Bindings Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json" + } + ] + } + } + }, + "tags": { + "type": "object", + "description": "An object to hold reusable Tag Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + } + } + }, + "externalDocs": { + "type": "object", + "description": "An object to hold reusable External Documentation Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + } + } + } + }, + "examples": [ + { + "components": { + "schemas": { + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "AvroExample": { + "schemaFormat": "application/vnd.apache.avro+json;version=1.9.0", + "schema": { + "$ref": "path/to/user-create.avsc#/UserCreate" + } + } + }, + "servers": { + "development": { + "host": "{stage}.in.mycompany.com:{port}", + "description": "RabbitMQ broker", + "protocol": "amqp", + "protocolVersion": "0-9-1", + "variables": { + "stage": { + "$ref": "#/components/serverVariables/stage" + }, + "port": { + "$ref": "#/components/serverVariables/port" + } + } + } + }, + "serverVariables": { + "stage": { + "default": "demo", + "description": "This value is assigned by the service provider, in this example `mycompany.com`" + }, + "port": { + "enum": [ + "5671", + "5672" + ], + "default": "5672" + } + }, + "channels": { + "user/signedup": { + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignUp" + } + } + } + }, + "messages": { + "userSignUp": { + "summary": "Action to sign a user up.", + "description": "Multiline description of what this action does.\nHere you have another line.\n", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + } + ], + "headers": { + "type": "object", + "properties": { + "applicationInstanceId": { + "description": "Unique identifier for a given instance of the publishing application", + "type": "string" + } + } + }, + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/userCreate" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + } + } + }, + "parameters": { + "userId": { + "description": "Id of the user." + } + }, + "correlationIds": { + "default": { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + } + }, + "messageTraits": { + "commonHeaders": { + "headers": { + "type": "object", + "properties": { + "my-app-header": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + } + } + } + } + } + ] + } + }, + "description": "!!Auto generated!! \n Do not manually edit. " +} diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json new file mode 100644 index 0000000000..c99755c283 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json @@ -0,0 +1,114 @@ +[ + { + "op": "add", + "path": "/$defs/binding/properties/type/enum/-", + "value": "asyncapi" + }, + { + "op": "add", + "path": "/$defs/binding/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "asyncapi" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "asyncapi" + }, + "kind": + { + "enum": [ "server", "client" ] + }, + "options": + { + "properties": + { + "specs": + { + "title": "Specifications", + "type": "array", + "items": + { + "type": "string" + } + }, + "tcp": "#/$defs/binding/tcp/options", + "tls": "#/$defs/binding/tls/options" + }, + "additionalProperties": false + }, + "routes": false + }, + "oneOf": + [ + { + "properties": + { + "kind": + { + "const": "server" + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "properties": + { + "routes": + { + "required": + [ + "exit" + ] + } + }, + "required": + [ + "routes" + ] + } + ] + }, + { + "properties": + { + "kind": + { + "const": "client" + }, + "routes": + { + "items": + { + "properties": + { + "exit": false + } + } + }, + "exit": false + } + } + ] + } + } + } +] diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/client.rpt new file mode 100644 index 0000000000..7e16896b71 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/client.rpt @@ -0,0 +1,92 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +connected + +read zilla:data.empty + + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} +write "asyncapiMessage" +write flush + + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()) + .build()} +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/server.rpt new file mode 100644 index 0000000000..6155cf6cb4 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/server.rpt @@ -0,0 +1,91 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +connected + +write zilla:data.empty +write flush + + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} +read "asyncapiMessage" + + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()) + .build()} + +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/client.rpt new file mode 100644 index 0000000000..c0de30bc54 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/client.rpt @@ -0,0 +1,88 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "duplex" + option zilla:ephemeral "test:composite0/mqtt" + +write zilla:begin.ext ${mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()} + +read zilla:begin.ext ${mqtt:matchBeginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()} + +connected + +read zilla:data.empty +read notify RECEIVED_SESSION_STATE + + +connect await RECEIVED_SESSION_STATE + "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "duplex" + option zilla:ephemeral "test:composite0/mqtt" + +write zilla:begin.ext ${mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()} + +connected + +write zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} + +write "asyncapiMessage" +write flush + + +connect await RECEIVED_SESSION_STATE + "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "duplex" + option zilla:ephemeral "test:composite0/mqtt" + +write zilla:begin.ext ${mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()} + +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/server.rpt new file mode 100644 index 0000000000..dc22830300 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/server.rpt @@ -0,0 +1,82 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${mqtt:matchBeginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()} + +write zilla:begin.ext ${mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()} + +connected + +write zilla:data.empty +write flush + + +accepted + +read zilla:begin.ext ${mqtt:matchBeginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()} + +connected + +read zilla:data.ext ${mqtt:matchDataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} + +read "asyncapiMessage" + + +accepted + +read zilla:begin.ext ${mqtt:matchBeginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()} + +connected diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java new file mode 100644 index 0000000000..f89d34769e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.nio.ByteBuffer; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; +import org.kaazing.k3po.lang.el.BytesMatcher; + +import io.aklivity.zilla.specs.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.specs.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; + +public class AsyncapiFunctionsTest +{ + @Test + public void shouldGetMapper() + { + AsyncapiFunctions.Mapper mapper = new AsyncapiFunctions.Mapper(); + assertEquals("asyncapi", mapper.getPrefixName()); + } + + @Test + public void shouldEncodeAsyncapiBeginExt() + { + final byte[] array = AsyncapiFunctions.beginEx() + .typeId(0) + .operationId("operationId") + .extension(new byte[] {1}) + .build(); + + DirectBuffer buffer = new UnsafeBuffer(array); + AsyncapiBeginExFW asyncapiBeginEx = new AsyncapiBeginExFW().wrap(buffer, 0, buffer.capacity()); + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + + assertEquals("operationId", asyncapiBeginEx.operationId().asString()); + assertEquals(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build(), + asyncapiBeginEx.extension()); + } + + @Test + public void shouldMatchAsyncapiBeginExtensionOnly() throws Exception + { + BytesMatcher matcher = AsyncapiFunctions.matchBeginEx() + .typeId(0x00) + .extension(new byte[] {1}) + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(11); + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + + new AsyncapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x00) + .extension(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } + + @Test + public void shouldMatchAsyncapiBeginExtension() throws Exception + { + BytesMatcher matcher = AsyncapiFunctions.matchBeginEx() + .typeId(0x00) + .apiId(1) + .operationId("operationId") + .extension(new byte[] {1}) + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(22); + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + + new AsyncapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x00) + .apiId(1) + .operationId("operationId") + .extension(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java new file mode 100644 index 0000000000..8e0a08d69b --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.JsonObject; + +import org.junit.Rule; +import org.junit.Test; + +import io.aklivity.zilla.specs.engine.config.ConfigSchemaRule; + +public class SchemaTest +{ + @Rule + public final ConfigSchemaRule schema = new ConfigSchemaRule() + .schemaPatch("io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json") + .schemaPatch("io/aklivity/zilla/specs/engine/schema/vault/test.schema.patch.json") + .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config"); + + @Test + public void shouldValidateClient() + { + JsonObject config = schema.validate("client.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateSecureClient() + { + JsonObject config = schema.validate("client.secure.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateServer() + { + JsonObject config = schema.validate("server.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateSecureServer() + { + JsonObject config = schema.validate("server.secure.yaml"); + + assertThat(config, not(nullValue())); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java new file mode 100644 index 0000000000..332a42dd35 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams.asyncapi; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${asyncapi}/publish.and.subscribe/client", + "${asyncapi}/publish.and.subscribe/server" + }) + public void shouldPublishAndSubscribe() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/MqttIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/MqttIT.java new file mode 100644 index 0000000000..d4bc19c18b --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/MqttIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams.mqtt; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class MqttIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + + @Test + @Specification({ + "${mqtt}/publish.and.subscribe/client", + "${mqtt}/publish.and.subscribe/server" + }) + public void shouldPublishAndSubscribe() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi/COPYRIGHT b/incubator/binding-asyncapi/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-asyncapi/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-asyncapi/LICENSE b/incubator/binding-asyncapi/LICENSE new file mode 100644 index 0000000000..f6abb6327b --- /dev/null +++ b/incubator/binding-asyncapi/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/binding-asyncapi/NOTICE b/incubator/binding-asyncapi/NOTICE new file mode 100644 index 0000000000..17478992e3 --- /dev/null +++ b/incubator/binding-asyncapi/NOTICE @@ -0,0 +1,18 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Jackson-dataformat-YAML under The Apache Software License, Version 2.0 + SnakeYAML under Apache License, Version 2.0 + diff --git a/incubator/binding-asyncapi/NOTICE.template b/incubator/binding-asyncapi/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/binding-asyncapi/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/binding-asyncapi/mvnw b/incubator/binding-asyncapi/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-asyncapi/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-asyncapi/mvnw.cmd b/incubator/binding-asyncapi/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-asyncapi/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-asyncapi/pom.xml b/incubator/binding-asyncapi/pom.xml new file mode 100644 index 0000000000..04ac004485 --- /dev/null +++ b/incubator/binding-asyncapi/pom.xml @@ -0,0 +1,317 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + binding-asyncapi + zilla::incubator::binding-asyncapi + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.60 + 4 + + + + + ${project.groupId} + binding-asyncapi.spec + ${project.version} + provided + + + ${project.groupId} + engine.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + io.aklivity.zilla + binding-mqtt + ${project.version} + provided + + + io.aklivity.zilla + binding-tcp + ${project.version} + provided + + + io.aklivity.zilla + binding-tls + ${project.version} + provided + + + io.aklivity.zilla + catalog-inline + ${project.version} + provided + + + io.aklivity.zilla + model-core + ${project.version} + provided + + + io.aklivity.zilla + model-json + ${project.version} + provided + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.16.1 + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core mqtt asyncapi + io.aklivity.zilla.runtime.binding.asyncapi.internal.types + + + + + generate + + + + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + binding-asyncapi.spec + + + ^\Qio/aklivity/zilla/specs/binding/asyncapi/\E + io/aklivity/zilla/runtime/binding/asyncapi/internal/ + + + + + io/aklivity/zilla/specs/binding/asyncapi/schema/*.json + ${project.build.directory}/classes + + + + extract-files + process-resources + + unpack + + + + + ${project.groupId} + binding-asyncapi.spec + ${project.version} + ${basedir}/target/test-classes + **\/*.yaml + + + + + + + + org.jasig.maven + maven-notice-plugin + + + com.mycila + license-maven-plugin + + + src/test/resources/io/aklivity/zilla/runtime/binding/asyncapi/internal/**/* + + + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/binding/asyncapi/internal/types/**/*.class + io/aklivity/zilla/runtime/binding/asyncapi/internal/model/*.class + io/aklivity/zilla/runtime/binding/asyncapi/internal/model2/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java new file mode 100644 index 0000000000..e23d8812ac --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; + +public class AsyncapiConfig +{ + public final String location; + public final Asyncapi asyncApi; + + public AsyncapiConfig( + String location, + Asyncapi asyncApi) + { + this.location = location; + this.asyncApi = asyncApi; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java new file mode 100644 index 0000000000..3723d7644b --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class AsyncapiOptionsConfig extends OptionsConfig +{ + public final List specs; + public final TcpOptionsConfig tcp; + public final TlsOptionsConfig tls; + + public static AsyncapiOptionsConfigBuilder builder() + { + return new AsyncapiOptionsConfigBuilder<>(AsyncapiOptionsConfig.class::cast); + } + + public static AsyncapiOptionsConfigBuilder builder( + Function mapper) + { + return new AsyncapiOptionsConfigBuilder<>(mapper); + } + + public AsyncapiOptionsConfig( + List specs, + TcpOptionsConfig tcp, + TlsOptionsConfig tls) + { + this.specs = specs; + this.tcp = tcp; + this.tls = tls; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java new file mode 100644 index 0000000000..86455c6e56 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class AsyncapiOptionsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + public List specs; + private TcpOptionsConfig tcp; + private TlsOptionsConfig tls; + + AsyncapiOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public AsyncapiOptionsConfigBuilder specs( + List specs) + { + this.specs = specs; + return this; + } + + public AsyncapiOptionsConfigBuilder tcp( + TcpOptionsConfig tcp) + { + this.tcp = tcp; + return this; + } + + public AsyncapiOptionsConfigBuilder tls( + TlsOptionsConfig tls) + { + this.tls = tls; + return this; + } + + + @Override + public T build() + { + return mapper.apply(new AsyncapiOptionsConfig(specs, tcp, tls)); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBinding.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBinding.java new file mode 100644 index 0000000000..45ed67c811 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBinding.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class AsyncapiBinding implements Binding +{ + public static final String NAME = "asyncapi"; + + private final AsyncapiConfiguration config; + + AsyncapiBinding( + AsyncapiConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return AsyncapiBinding.NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/asyncapi.schema.patch.json"); + } + + @Override + public String originType( + KindConfig kind) + { + return kind == KindConfig.CLIENT ? NAME : null; + } + + @Override + public String routedType( + KindConfig kind) + { + return kind == KindConfig.SERVER ? NAME : null; + } + + @Override + public AsyncapiBindingContext supply( + EngineContext context) + { + return new AsyncapiBindingContext(config, context); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java new file mode 100644 index 0000000000..c29cd11e35 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import java.util.EnumMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public class AsyncapiBindingAdapter implements CompositeBindingAdapterSpi +{ + private final Map> composites; + + public AsyncapiBindingAdapter() + { + Map> composites = new EnumMap<>(KindConfig.class); + composites.put(SERVER, new AsyncapiServerCompositeBindingAdapter()::adapt); + composites.put(CLIENT, new AsyncapiClientCompositeBindingAdapter()::adapt); + this.composites = composites; + } + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + return composites.get(binding.kind).apply(binding); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java new file mode 100644 index 0000000000..4e7b99c174 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import java.util.EnumMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiClientFactory; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiServerFactory; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiStreamFactory; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +final class AsyncapiBindingContext implements BindingContext +{ + private final Map factories; + + AsyncapiBindingContext( + AsyncapiConfiguration config, + EngineContext context) + { + Map factories = new EnumMap<>(KindConfig.class); + factories.put(SERVER, new AsyncapiServerFactory(config, context)); + factories.put(CLIENT, new AsyncapiClientFactory(config, context)); + this.factories = factories; + } + + @Override + public BindingHandler attach( + BindingConfig binding) + { + AsyncapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.attach(binding); + } + + return factory; + } + + @Override + public void detach( + BindingConfig binding) + { + AsyncapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.detach(binding.id); + } + } + + @Override + public String toString() + { + return String.format("%s %s", getClass().getSimpleName(), factories); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingFactorySpi.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingFactorySpi.java new file mode 100644 index 0000000000..e55fb89fd0 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingFactorySpi.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import io.aklivity.zilla.runtime.common.feature.Incubating; +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi; + +@Incubating +public final class AsyncapiBindingFactorySpi implements BindingFactorySpi +{ + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public AsyncapiBinding create( + Configuration config) + { + return new AsyncapiBinding(new AsyncapiConfiguration(config)); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java new file mode 100644 index 0000000000..3809ac126e --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; + +public class AsyncapiClientCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; + AsyncapiConfig asyncapiConfig = options.specs.get(0); + this.asyncApi = asyncapiConfig.asyncApi; + + int[] mqttsPorts = resolvePortsForScheme("mqtts"); + this.isTlsEnabled = mqttsPorts != null; + this.qname = binding.qname; + this.qvault = binding.qvault; + + return BindingConfig.builder(binding) + .composite() + .name(String.format(qname, "$composite")) + .binding() + .name("mqtt_client0") + .type("mqtt") + .kind(CLIENT) + .exit(isTlsEnabled ? "tls_client0" : "tcp_client0") + .build() + .inject(n -> injectTlsClient(n, options)) + .binding() + .name("tcp_client0") + .type("tcp") + .kind(CLIENT) + .options(options.tcp) + .build() + .build() + .build(); + } + + private NamespaceConfigBuilder injectTlsClient( + NamespaceConfigBuilder namespace, + AsyncapiOptionsConfig options) + { + if (isTlsEnabled) + { + namespace + .binding() + .name("tls_client0") + .type("tls") + .kind(CLIENT) + .options(options.tls) + .vault(qvault) + .exit("tcp_client0") + .build(); + } + return namespace; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java new file mode 100644 index 0000000000..d4e9245b87 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static java.util.Objects.requireNonNull; + +import java.net.URI; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; + +public class AsyncapiCompositeBindingAdapter +{ + protected static final String INLINE_CATALOG_NAME = "catalog0"; + protected static final String APPLICATION_JSON = "application/json"; + protected static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); + + protected Asyncapi asyncApi; + protected boolean isTlsEnabled; + protected String qname; + protected String qvault; + + + protected int[] resolveAllPorts() + { + int[] ports = new int[asyncApi.servers.size()]; + String[] keys = asyncApi.servers.keySet().toArray(String[]::new); + for (int i = 0; i < asyncApi.servers.size(); i++) + { + AsyncapiServerView server = AsyncapiServerView.of(asyncApi.servers.get(keys[i])); + URI url = server.url(); + ports[i] = url.getPort(); + } + return ports; + } + + protected int[] resolvePortsForScheme( + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + protected URI findFirstServerUrlWithScheme( + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (String key : asyncApi.servers.keySet()) + { + AsyncapiServerView server = AsyncapiServerView.of(asyncApi.servers.get(key)); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfiguration.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfiguration.java new file mode 100644 index 0000000000..bf602321da --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class AsyncapiConfiguration extends Configuration +{ + public static final LongPropertyDef ASYNCAPI_TARGET_ROUTE_ID; + private static final ConfigurationDef ASYNCAPI_CONFIG; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.binding.asyncapi"); + ASYNCAPI_TARGET_ROUTE_ID = config.property("target.route.id", -1L); + ASYNCAPI_CONFIG = config; + } + + public AsyncapiConfiguration( + Configuration config) + { + super(ASYNCAPI_CONFIG, config); + } + + public long targetRouteId() + { + return ASYNCAPI_TARGET_ROUTE_ID.getAsLong(this); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java new file mode 100644 index 0000000000..ed262d71e7 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java @@ -0,0 +1,235 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiChannel; +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.mqtt.config.MqttConditionConfig; +import io.aklivity.zilla.runtime.binding.mqtt.config.MqttOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpConditionConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public class AsyncapiServerCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private int[] mqttPorts; + private int[] mqttsPorts; + private boolean isPlainEnabled; + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; + AsyncapiConfig asyncapiConfig = options.specs.get(0); + this.asyncApi = asyncapiConfig.asyncApi; + + int[] allPorts = resolveAllPorts(); + this.mqttPorts = resolvePortsForScheme("mqtt"); + this.mqttsPorts = resolvePortsForScheme("mqtts"); + this.isPlainEnabled = mqttPorts != null; + this.isTlsEnabled = mqttsPorts != null; + this.qname = binding.qname; + this.qvault = binding.qvault; + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/mqtt", qname)) + .binding() + .name("tcp_server0") + .type("tcp") + .kind(SERVER) + .options(TcpOptionsConfig::builder) + .host("0.0.0.0") + .ports(allPorts) + .build() + .inject(this::injectPlainTcpRoute) + .inject(this::injectTlsTcpRoute) + .build() + .inject(n -> injectTlsServer(n, options)) + .binding() + .name("mqtt_server0") + .type("mqtt") + .kind(SERVER) + .inject(this::injectMqttServerOptions) + .inject(this::injectMqttServerRoutes) + .build() + .build() + .build(); + } + + private BindingConfigBuilder injectPlainTcpRoute( + BindingConfigBuilder binding) + { + if (isPlainEnabled) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(mqttPorts) + .build() + .exit("mqtt_server0") + .build(); + } + return binding; + } + + private BindingConfigBuilder injectTlsTcpRoute( + BindingConfigBuilder binding) + { + if (isTlsEnabled) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(mqttsPorts) + .build() + .exit("tls_server0") + .build(); + } + return binding; + } + + private NamespaceConfigBuilder injectTlsServer( + NamespaceConfigBuilder namespace, + AsyncapiOptionsConfig options) + { + if (isTlsEnabled) + { + namespace + .binding() + .name("tls_server0") + .type("tls") + .kind(SERVER) + .options(TlsOptionsConfig::builder) + .keys(options.tls.keys) + .sni(options.tls.sni) + .alpn(options.tls.alpn) + .build() + .vault(qvault) + .exit("mqtt_server0") + .build(); + } + return namespace; + } + + private BindingConfigBuilder injectMqttServerOptions( + BindingConfigBuilder binding) + { + for (Map.Entry channelEntry : asyncApi.channels.entrySet()) + { + String topic = channelEntry.getValue().address.replaceAll("\\{[^}]+\\}", "#"); + Map messages = channelEntry.getValue().messages; + if (hasJsonContentType()) + { + binding + .options(MqttOptionsConfig::builder) + .topic() + .name(topic) + .content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .inject(cataloged -> injectJsonSchemas(cataloged, messages, APPLICATION_JSON)) + .build() + .build() + .build() + .build() + .build(); + } + } + return binding; + } + + private CatalogedConfigBuilder injectJsonSchemas( + CatalogedConfigBuilder cataloged, + Map messages, + String contentType) + { + for (Map.Entry messageEntry : messages.entrySet()) + { + AsyncapiMessageView message = AsyncapiMessageView.of(asyncApi.components.messages, messageEntry.getValue()); + String schema = messageEntry.getKey(); + if (message.contentType().equals(contentType)) + { + cataloged + .schema() + .subject(schema) + .build() + .build(); + } + else + { + throw new RuntimeException("Invalid content type"); + } + } + return cataloged; + } + + private BindingConfigBuilder injectMqttServerRoutes( + BindingConfigBuilder binding) + { + for (Map.Entry entry : asyncApi.channels.entrySet()) + { + String topic = entry.getValue().address.replaceAll("\\{[^}]+\\}", "#"); + binding + .route() + .when(MqttConditionConfig::builder) + .publish() + .topic(topic) + .build() + .build() + .when(MqttConditionConfig::builder) + .subscribe() + .topic(topic) + .build() + .build() + .exit(qname) + .build(); + } + return binding; + } + + private boolean hasJsonContentType() + { + String contentType = null; + if (asyncApi.components != null && asyncApi.components.messages != null && + !asyncApi.components.messages.isEmpty()) + { + AsyncapiMessage firstAsyncapiMessage = asyncApi.components.messages.entrySet().stream() + .findFirst().get().getValue(); + contentType = AsyncapiMessageView.of(asyncApi.components.messages, firstAsyncapiMessage).contentType(); + } + return contentType != null && jsonContentType.reset(contentType).matches(); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java new file mode 100644 index 0000000000..09d31cfd2c --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static java.util.stream.Collector.of; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; + +import java.util.List; + +import org.agrona.collections.IntHashSet; +import org.agrona.collections.Long2LongHashMap; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class AsyncapiBindingConfig +{ + public final long id; + public final String name; + public final KindConfig kind; + public final AsyncapiOptionsConfig options; + public final List routes; + private final IntHashSet composites; + private final long overrideRouteId; + private final Long2LongHashMap resolvedIds; + + public AsyncapiBindingConfig( + BindingConfig binding, + long overrideRouteId) + { + this.id = binding.id; + this.name = binding.name; + this.kind = binding.kind; + this.overrideRouteId = overrideRouteId; + this.options = AsyncapiOptionsConfig.class.cast(binding.options); + this.routes = binding.routes.stream().map(AsyncapiRouteConfig::new).collect(toList()); + this.resolvedIds = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("mqtt")) + .collect(of( + () -> new Long2LongHashMap(-1), + (m, r) -> m.put(0L, r.id), //TODO: populate proper apiId + (m, r) -> m, + IDENTITY_FINISH + )); + this.composites = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("mqtt")) + .map(b -> NamespacedId.namespaceId(b.id)) + .collect(toCollection(IntHashSet::new)); + } + + public boolean isCompositeOriginId( + long originId) + { + return composites.contains(NamespacedId.namespaceId(originId)); + } + + public long resolveResolvedId( + long apiId) + { + return overrideRouteId != -1 ? overrideRouteId : resolvedIds.get(apiId); + } + + public AsyncapiRouteConfig resolve( + long authorization) + { + return routes.stream() + .filter(r -> r.authorized(authorization)) + .findFirst() + .orElse(null); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java new file mode 100644 index 0000000000..4bb1288060 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java @@ -0,0 +1,253 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static java.util.stream.Collectors.toList; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonReader; +import jakarta.json.JsonString; +import jakarta.json.JsonStructure; +import jakarta.json.JsonValue; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.JsonValidationService; +import org.leadpony.justify.api.ProblemHandler; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.ConfigException; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public final class AsyncapiOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + private static final String SPECS_NAME = "specs"; + private static final String TCP_NAME = "tcp"; + private static final String TLS_NAME = "tls"; + + private OptionsConfigAdapter tcpOptions; + private OptionsConfigAdapter tlsOptions; + private Function readURL; + + public Kind kind() + { + return Kind.BINDING; + } + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + AsyncapiOptionsConfig asyncapiOptions = (AsyncapiOptionsConfig) options; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (asyncapiOptions.specs != null) + { + JsonArrayBuilder keys = Json.createArrayBuilder(); + asyncapiOptions.specs.forEach(p -> keys.add(p.location)); + object.add(SPECS_NAME, keys); + } + + if (asyncapiOptions.tcp != null) + { + final TcpOptionsConfig tcp = asyncapiOptions.tcp; + object.add(TCP_NAME, tcpOptions.adaptToJson(tcp)); + } + + if (asyncapiOptions.tls != null) + { + final TlsOptionsConfig tls = asyncapiOptions.tls; + object.add(TLS_NAME, tlsOptions.adaptToJson(tls)); + } + + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + final AsyncapiOptionsConfigBuilder asyncapiOptions = AsyncapiOptionsConfig.builder(); + + List specs = object.containsKey(SPECS_NAME) + ? asListAsyncapis(object.getJsonArray(SPECS_NAME)) + : null; + asyncapiOptions.specs(specs); + + if (object.containsKey(TCP_NAME)) + { + final JsonObject tcp = object.getJsonObject(TCP_NAME); + final TcpOptionsConfig tcpOptions = (TcpOptionsConfig) this.tcpOptions.adaptFromJson(tcp); + asyncapiOptions.tcp(tcpOptions); + } + + if (object.containsKey(TLS_NAME)) + { + final JsonObject tls = object.getJsonObject(TLS_NAME); + final TlsOptionsConfig tlsOptions = (TlsOptionsConfig) this.tlsOptions.adaptFromJson(tls); + asyncapiOptions.tls(tlsOptions); + } + + return asyncapiOptions.build(); + } + + @Override + public void adaptContext( + ConfigAdapterContext context) + { + this.readURL = context::readURL; + this.tcpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tcpOptions.adaptType("tcp"); + this.tlsOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tlsOptions.adaptType("tls"); + } + + private List asListAsyncapis( + JsonArray array) + { + return array.stream() + .map(this::asAsyncapi) + .collect(toList()); + } + + private AsyncapiConfig asAsyncapi( + JsonValue value) + { + final String location = ((JsonString) value).getString(); + final String specText = readURL.apply(location); + Asyncapi asyncapi = parseAsyncapi(specText); + + return new AsyncapiConfig(location, asyncapi); + } + + private Asyncapi parseAsyncapi( + String asyncapiText) + { + Asyncapi asyncapi = null; + if (validateAsyncapiSchema(asyncapiText)) + { + try (Jsonb jsonb = JsonbBuilder.create()) + { + asyncapi = jsonb.fromJson(asyncapiText, Asyncapi.class); + } + catch (Exception ex) + { + rethrowUnchecked(ex); + } + } + return asyncapi; + } + + private boolean validateAsyncapiSchema( + String asyncapiText) + { + List errors = new LinkedList<>(); + + boolean valid = false; + + try + { + JsonValidationService service = JsonValidationService.newInstance(); + + String version = detectAsyncapiVersion(asyncapiText); + InputStream schemaInput = selectSchemaPathForVersion(version); + + JsonSchema schema = service.readSchema(schemaInput); + ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); + + String readable = asyncapiText.stripTrailing(); + Reader asyncapiReader = new StringReader(readable); + + JsonReader reader = service.createReader(asyncapiReader, schema, handler); + + JsonStructure json = reader.read(); + valid = json != null; + } + catch (Exception ex) + { + errors.add(ex); + } + + return valid; + } + + private String detectAsyncapiVersion( + String asyncapiText) + { + try (JsonReader reader = Json.createReader(new StringReader(asyncapiText))) + { + JsonObject json = reader.readObject(); + if (json.containsKey("asyncapi")) + { + return json.getString("asyncapi"); + } + else + { + throw new IllegalArgumentException("Unable to determine AsyncAPI version."); + } + } + catch (Exception e) + { + throw new RuntimeException("Error reading AsyncAPI document.", e); + } + } + + private InputStream selectSchemaPathForVersion( + String version) + { + if (version.startsWith("3.0")) + { + return AsyncapiBinding.class.getResourceAsStream("schema/asyncapi.3.0.schema.json"); + } + else if (version.startsWith("2.6")) + { + return AsyncapiBinding.class.getResourceAsStream("schema/asyncapi.2.6.schema.json"); + } + else + { + throw new IllegalArgumentException("Unsupported AsyncAPI version: " + version); + } + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java new file mode 100644 index 0000000000..b1b54aa41d --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + + +import java.util.function.LongPredicate; + +import io.aklivity.zilla.runtime.engine.config.RouteConfig; + +public final class AsyncapiRouteConfig +{ + public final long id; + + private final LongPredicate authorized; + + public AsyncapiRouteConfig( + RouteConfig route) + { + this.id = route.id; + this.authorized = route.authorized; + } + + boolean authorized( + long authorization) + { + return authorized.test(authorization); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/Asyncapi.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/Asyncapi.java new file mode 100644 index 0000000000..0894282dac --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/Asyncapi.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.Map; + +public class Asyncapi +{ + public Map servers; + public Map channels; + public Map operations; + public AsyncapiComponents components; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiBinding.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiBinding.java new file mode 100644 index 0000000000..29eb4fe648 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiBinding.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +public class AsyncapiBinding +{ + public String method; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiChannel.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiChannel.java new file mode 100644 index 0000000000..ddfc9021f1 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiChannel.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.LinkedHashMap; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class AsyncapiChannel +{ + public String address; + public LinkedHashMap messages; + public LinkedHashMap parameters; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiComponents.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiComponents.java new file mode 100644 index 0000000000..aeaa0e9c6b --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiComponents.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.Map; + +public class AsyncapiComponents +{ + public Map securitySchemes; + public Map messages; + public Map schemas; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiItem.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiItem.java new file mode 100644 index 0000000000..dbdff777ee --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiItem.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +public class AsyncapiItem +{ + public String type; + public String description; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java new file mode 100644 index 0000000000..b0f3b8f93c --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class AsyncapiMessage +{ + public AsyncapiSchema headers; + public String contentType; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java new file mode 100644 index 0000000000..d7f721fb9c --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.Map; + +public class AsyncapiOperation +{ + public Map bindings; + public AsyncapiChannel asyncapiChannel; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java new file mode 100644 index 0000000000..f915b15bc0 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +public class AsyncapiParameter +{ + public AsyncapiSchema asyncapiSchema; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSchema.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSchema.java new file mode 100644 index 0000000000..6440924e6d --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSchema.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class AsyncapiSchema +{ + public String type; + public AsyncapiSchema items; + public Map properties; + public List required; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSecurityScheme.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSecurityScheme.java new file mode 100644 index 0000000000..96ba909021 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSecurityScheme.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +public class AsyncapiSecurityScheme +{ + public String bearerFormat; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java new file mode 100644 index 0000000000..d9a85e1935 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.List; +import java.util.Map; + +public class AsyncapiServer +{ + public String host; + public List>> security; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java new file mode 100644 index 0000000000..bc448d830a --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java @@ -0,0 +1,1067 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfiguration; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiBindingConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class AsyncapiClientFactory implements AsyncapiStreamFactory +{ + private static final String MQTT_TYPE_NAME = "mqtt"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); + + private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); + + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int asyncapiTypeId; + private final int mqttTypeId; + private final AsyncapiConfiguration config; + + + public AsyncapiClientFactory( + AsyncapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); + this.mqttTypeId = context.supplyTypeId(MQTT_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return mqttTypeId; + } + + @Override + public int routedTypeId() + { + return asyncapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + AsyncapiBindingConfig asyncapiBinding = new AsyncapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, asyncapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + + final AsyncapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + final long apiId = asyncapiBeginEx.apiId(); + final String operationId = asyncapiBeginEx.operationId().asString(); + + final long resolvedId = binding.resolveResolvedId(apiId); + + if (resolvedId != -1) + { + newStream = new AsyncapiStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + resolvedId, + operationId)::onAsyncapiMessage; + } + + } + + return newStream; + } + + private final class AsyncapiStream + { + private final CompositeStream composite; + private final MessageConsumer sender; + private final String operationId; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long replyBudgetId; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private AsyncapiStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + String operationId) + { + this.composite = new CompositeStream(this, routedId, resolvedId, authorization); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onAsyncapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiReset(reset); + break; + default: + break; + } + } + + private void onAsyncapiBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final long authorization = begin.authorization(); + final long affinity = begin.affinity(); + final OctetsFW extension = begin.extension(); + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = AsyncapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + composite.doCompositeBegin(traceId, asyncapiBeginEx.extension()); + } + + private void onAsyncapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + composite.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onAsyncapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + composite.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + composite.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + composite.doCompositeAbort(traceId, extension); + } + + private void onAsyncapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = AsyncapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onAsyncapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + composite.doCompositeWindow(traceId, acknowledge, budgetId, padding); + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + state = AsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .operationId(operationId) + .extension(extension) + .build(); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, asyncapiBeginEx); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.initialClosed(state)) + { + state = AsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doCompositeWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = composite.initialAck; + initialMax = composite.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void doCompositeData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = AsyncapiState.closeReply(state); + } + + private void doCompositeAbort( + long traceId) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = AsyncapiState.closeInitial(state); + } + + private void doAsyncapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, reserved, extension); + } + + private void cleanup( + long traceId) + { + doCompositeReset(traceId); + doCompositeAbort(traceId); + + composite.cleanup(traceId); + } + } + + final class CompositeStream + { + private final AsyncapiStream delegate; + private final long originId; + private final long routedId; + private final long authorization; + + private long initialId; + private long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeStream( + AsyncapiStream delegate, + long originId, + long routedId, + long authorization) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialOpening(state)) + { + assert state == 0; + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.receiver = newStream(this::onCompositeMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, 0L, extension); + state = AsyncapiState.openingInitial(state); + } + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = AsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doCompositeReset(traceId); + } + + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = AsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeWindow(authorization, traceId, budgetId, padding); + } + + private void onCompositeMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = AsyncapiState.openingReply(state); + + delegate.doCompositeBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeData(traceId, flags, reserved, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doAsyncapiFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeAbort(traceId); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = AsyncapiState.closeReply(state); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java new file mode 100644 index 0000000000..c5470ef7c7 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java @@ -0,0 +1,1068 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfiguration; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiBindingConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiRouteConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.MqttBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class AsyncapiServerFactory implements AsyncapiStreamFactory +{ + private static final String MQTT_TYPE_NAME = "mqtt"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); + private final MqttBeginExFW mqttBeginExRO = new MqttBeginExFW(); + + private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); + + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int asyncapiTypeId; + private final int mqttTypeId; + private final AsyncapiConfiguration config; + + public AsyncapiServerFactory( + AsyncapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); + this.mqttTypeId = context.supplyTypeId(MQTT_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return mqttTypeId; + } + + @Override + public int routedTypeId() + { + return asyncapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + AsyncapiBindingConfig asyncapiBinding = new AsyncapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, asyncapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final MqttBeginExFW mqttBeginEx = extension.get(mqttBeginExRO::tryWrap); + + final AsyncapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null && binding.isCompositeOriginId(originId)) + { + final AsyncapiRouteConfig route = binding.resolve(authorization); + + if (route != null) + { + final String operationId = null; + + newStream = new CompositeStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + route.id, + operationId)::onCompositeMessage; + } + + } + + return newStream; + } + + private final class CompositeStream + { + private final AsyncapiStream delegate; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private CompositeStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + String operationId) + { + this.delegate = new AsyncapiStream(this, routedId, resolvedId, authorization, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onCompositeMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final long authorization = begin.authorization(); + final long affinity = begin.affinity(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = AsyncapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiEnd(traceId, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiFlush(traceId, reserved, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiAbort(traceId, extension); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.initialClosed(state)) + { + state = AsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doCompositeWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = delegate.initialAck; + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + state = AsyncapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doCompositeData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = AsyncapiState.closeReply(state); + } + + private void doCompositeAbort( + long traceId) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = AsyncapiState.closeInitial(state); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = AsyncapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiWindow(traceId, acknowledge, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doCompositeReset(traceId); + doCompositeAbort(traceId); + + delegate.cleanup(traceId); + } + } + + final class AsyncapiStream + { + private final CompositeStream delegate; + private final String operationId; + private final long originId; + private final long routedId; + private final long authorization; + + private long initialId; + private long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private AsyncapiStream( + CompositeStream delegate, + long originId, + long routedId, + long authorization, + String operationId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onAsyncapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiWindow(window); + break; + default: + break; + } + } + + private void onAsyncapiBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = AsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + final OctetsFW asyncapiExtension = asyncapiBeginEx != null ? asyncapiBeginEx.extension() : EMPTY_OCTETS; + + delegate.doCompositeBegin(traceId, asyncapiExtension); + } + + private void onAsyncapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeData(traceId, flags, reserved, payload, extension); + } + + private void onAsyncapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeAbort(traceId); + } + + private void onAsyncapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = AsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doCompositeReset(traceId); + } + + + private void onAsyncapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = AsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeWindow(authorization, traceId, budgetId, padding); + } + + private void doAsyncapiReset( + long traceId) + { + if (!AsyncapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = AsyncapiState.closeReply(state); + } + } + + private void doAsyncapiWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doAsyncapiBegin( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialOpening(state)) + { + assert state == 0; + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .operationId(operationId) + .extension(extension) + .build(); + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.receiver = newStream(this::onAsyncapiMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, 0L, asyncapiBeginEx); + state = AsyncapiState.openingInitial(state); + } + } + + private void doAsyncapiData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doAsyncapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doAsyncapiEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void doAsyncapiAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doAsyncapiAbort(traceId, EMPTY_OCTETS); + doAsyncapiReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiState.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiState.java new file mode 100644 index 0000000000..8a8724fd20 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiState.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +public final class AsyncapiState +{ + private static final int INITIAL_OPENING = 0x10; + private static final int INITIAL_OPENED = 0x20; + private static final int INITIAL_CLOSING = 0x40; + private static final int INITIAL_CLOSED = 0x80; + private static final int REPLY_OPENED = 0x01; + private static final int REPLY_OPENING = 0x02; + private static final int REPLY_CLOSING = 0x04; + private static final int REPLY_CLOSED = 0x08; + + static int openingInitial( + int state) + { + return state | INITIAL_OPENING; + } + + static int openInitial( + int state) + { + return openingInitial(state) | INITIAL_OPENED; + } + + static boolean initialOpening( + int state) + { + return (state & INITIAL_OPENING) != 0; + } + + static boolean initialOpened( + int state) + { + return (state & INITIAL_OPENED) != 0; + } + + static int closingInitial( + int state) + { + return state | INITIAL_CLOSING; + } + + static int closeInitial( + int state) + { + return closingInitial(state) | INITIAL_CLOSED; + } + + static boolean initialClosing( + int state) + { + return (state & INITIAL_CLOSING) != 0; + } + + static boolean initialClosed( + int state) + { + return (state & INITIAL_CLOSED) != 0; + } + + static int openingReply( + int state) + { + return state | REPLY_OPENING; + } + + static int openReply( + int state) + { + return openingReply(state) | REPLY_OPENED; + } + + static boolean replyOpening( + int state) + { + return (state & REPLY_OPENING) != 0; + } + + static boolean replyOpened( + int state) + { + return (state & REPLY_OPENED) != 0; + } + + static int closingReply( + int state) + { + return state | REPLY_CLOSING; + } + + static int closeReply( + int state) + { + return closingReply(state) | REPLY_CLOSED; + } + + static boolean replyClosing( + int state) + { + return (state & REPLY_CLOSING) != 0; + } + + static boolean replyClosed( + int state) + { + return (state & REPLY_CLOSED) != 0; + } + + static boolean closed( + int state) + { + return initialClosed(state) && replyClosed(state); + } + + private AsyncapiState() + { + // utility + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiStreamFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiStreamFactory.java new file mode 100644 index 0000000000..7a8c299e21 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiStreamFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public interface AsyncapiStreamFactory extends BindingHandler +{ + void attach( + BindingConfig binding); + + void detach( + long bindingId); +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiChannelView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiChannelView.java new file mode 100644 index 0000000000..7b087a05ad --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiChannelView.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiChannel; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiParameter; + +public final class AsyncapiChannelView extends AsyncapiResolvable +{ + private final AsyncapiChannel channel; + + public String address() + { + return channel.address; + } + + public Map messages() + { + return channel.messages; + } + + public Map parameters() + { + return channel.parameters; + } + + public static AsyncapiChannelView of( + Map channels, + AsyncapiChannel asyncapiChannel) + { + return new AsyncapiChannelView(channels, asyncapiChannel); + } + + private AsyncapiChannelView( + Map channels, + AsyncapiChannel channel) + { + super(channels, "#/channels/(\\w+)"); + this.channel = channel.ref == null ? channel : resolveRef(channel.ref); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java new file mode 100644 index 0000000000..e5535ea784 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSchema; + +public final class AsyncapiMessageView extends AsyncapiResolvable +{ + private final AsyncapiMessage message; + + public String refKey() + { + return key; + } + + public AsyncapiSchema headers() + { + return message.headers; + } + + public String contentType() + { + return message.contentType; + } + + public static AsyncapiMessageView of( + Map messages, + AsyncapiMessage asyncapiMessage) + { + return new AsyncapiMessageView(messages, asyncapiMessage); + } + + private AsyncapiMessageView( + Map messages, + AsyncapiMessage message) + { + super(messages, "#/components/messages/(\\w+)"); + this.message = message.ref == null ? message : resolveRef(message.ref); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiResolvable.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiResolvable.java new file mode 100644 index 0000000000..2334037c55 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiResolvable.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class AsyncapiResolvable +{ + private final Map map; + private final Matcher matcher; + + protected String key; + + public AsyncapiResolvable( + Map map, + String regex) + { + this.map = map; + this.matcher = Pattern.compile(regex).matcher(""); + } + + protected T resolveRef( + String ref) + { + T result = null; + if (matcher.reset(ref).matches()) + { + key = matcher.group(1); + result = map.get(key); + } + return result; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java new file mode 100644 index 0000000000..d92de500f8 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbPropertyOrder; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiItem; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSchema; + +@JsonbPropertyOrder({ + "type", + "items", + "properties", + "required" +}) +public final class AsyncapiSchemaView extends AsyncapiResolvable +{ + private static final String ARRAY_TYPE = "array"; + + private final AsyncapiSchema schema; + private final Map schemas; + + public String getType() + { + return schema.type; + } + + public AsyncapiSchemaView getItems() + { + return schema.items == null ? null : AsyncapiSchemaView.of(schemas, schema.items); + } + + public Map getProperties() + { + return schema.properties; + } + + public List getRequired() + { + return schema.required; + } + + public static AsyncapiSchemaView of( + Map schemas, + AsyncapiSchema asyncapiSchema) + { + return new AsyncapiSchemaView(schemas, asyncapiSchema); + } + + private AsyncapiSchemaView( + Map schemas, + AsyncapiSchema schema) + { + super(schemas, "#/components/schemas/(\\w+)"); + if (schema.ref != null) + { + schema = resolveRef(schema.ref); + } + else if (ARRAY_TYPE.equals(schema.type) && schema.items != null && schema.items.ref != null) + { + schema.items = resolveRef(schema.items.ref); + } + this.schemas = schemas; + this.schema = schema; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java new file mode 100644 index 0000000000..47d91d02d9 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiServer; + +public final class AsyncapiServerView +{ + private final AsyncapiServer server; + + public URI url() + { + return URI.create(server.host); + } + + public List>> security() + { + return server.security; + } + + public String scheme() + { + return url().getScheme(); + } + + public String authority() + { + return String.format("%s:%d", url().getHost(), url().getPort()); + } + + public static AsyncapiServerView of( + AsyncapiServer asyncapiServer) + { + return new AsyncapiServerView(asyncapiServer); + } + + private AsyncapiServerView( + AsyncapiServer server) + { + this.server = server; + } +} diff --git a/incubator/binding-asyncapi/src/main/moditect/module-info.java b/incubator/binding-asyncapi/src/main/moditect/module-info.java new file mode 100644 index 0000000000..046666982b --- /dev/null +++ b/incubator/binding-asyncapi/src/main/moditect/module-info.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.binding.asyncapi +{ + requires io.aklivity.zilla.runtime.engine; + requires io.aklivity.zilla.runtime.binding.mqtt; + requires io.aklivity.zilla.runtime.binding.tcp; + requires io.aklivity.zilla.runtime.binding.tls; + + opens io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + + exports io.aklivity.zilla.runtime.binding.asyncapi.config; + + provides io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiOptionsConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingAdapter; +} diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi new file mode 100644 index 0000000000..e932368241 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingFactorySpi diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi new file mode 100644 index 0000000000..bdc77aaba9 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingAdapter diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..3a362743bc --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiOptionsConfigAdapter diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java new file mode 100644 index 0000000000..cc1e44bff7 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiOptionsConfigAdapterTest; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public class AsyncapiBingingFactorySpiTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + private Jsonb jsonb; + + @Before + public void initJson() throws IOException + { + String content; + try (InputStream resource = AsyncapiOptionsConfigAdapterTest.class + .getResourceAsStream("../../../../../specs/binding/asyncapi/config/mqtt/asyncapi.yaml")) + { + content = new String(resource.readAllBytes(), UTF_8); + } + Mockito.doReturn(content).when(context).readURL("mqtt/asyncapi.yaml"); + + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("asyncapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldAddCompositeBinding() + { + String text = + "{" + + "\"specs\":" + + "[" + + "\"mqtt/asyncapi.yaml\"" + + "]" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + BindingConfig config = BindingConfig.builder() + .namespace("example") + .name("asyncapi0") + .type("asyncapi") + .kind(KindConfig.SERVER) + .options(options) + .build(); + + AsyncapiServerCompositeBindingAdapter adapter = new AsyncapiServerCompositeBindingAdapter(); + Assert.assertEquals(0, config.composites.size()); + BindingConfig newConfig = adapter.adapt(config); + Assert.assertEquals(1, newConfig.composites.size()); + Assert.assertEquals(2, newConfig.composites.get(0).bindings.size()); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfigurationTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfigurationTest.java new file mode 100644 index 0000000000..0d04c63d37 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfigurationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfiguration.ASYNCAPI_TARGET_ROUTE_ID; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class AsyncapiConfigurationTest +{ + public static final String ASYNCAPI_TARGET_ROUTE_ID_NAME = "zilla.binding.asyncapi.target.route.id"; + + @Test + public void shouldVerifyConstants() throws Exception + { + assertEquals(ASYNCAPI_TARGET_ROUTE_ID.name(), ASYNCAPI_TARGET_ROUTE_ID_NAME); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..54b6bd4af9 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java @@ -0,0 +1,188 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +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.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.specs.binding.asyncapi.AsyncapiSpecs; + +public class AsyncapiOptionsConfigAdapterTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + private Jsonb jsonb; + + @Before + public void initJson() throws IOException + { + try (InputStream resource = AsyncapiSpecs.class + .getResourceAsStream("config/mqtt/asyncapi.yaml")) + { + String content = new String(resource.readAllBytes(), UTF_8); + Mockito.doReturn(content).when(context).readURL("mqtt/asyncapi.yaml"); + + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("asyncapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + } + + @Test + public void shouldReadOptions() + { + String text = + "{" + + "\"specs\":" + + "[" + + "\"mqtt/asyncapi.yaml\"" + + "]," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":7183" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"mqtt.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"mqtt\"" + + "]" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + assertThat(options, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.get(0); + assertThat(asyncapi.location, equalTo("mqtt/asyncapi.yaml")); + assertThat(asyncapi.asyncApi, instanceOf(Asyncapi.class)); + assertThat(options.tcp.host, equalTo("localhost")); + assertThat(options.tcp.ports, equalTo(new int[] { 7183 })); + assertThat(options.tls.keys, equalTo(asList("localhost"))); + assertThat(options.tls.trust, equalTo(asList("serverca"))); + assertThat(options.tls.trustcacerts, equalTo(true)); + assertThat(options.tls.sni, equalTo(asList("mqtt.example.net"))); + assertThat(options.tls.alpn, equalTo(asList("mqtt"))); + } + + @Test + public void shouldWriteOptions() + { + List specs = new ArrayList<>(); + specs.add(new AsyncapiConfig("mqtt/asyncapi.yaml", new Asyncapi())); + + + AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() + .inject(Function.identity()) + .specs(specs) + .tcp(TcpOptionsConfig.builder() + .host("localhost") + .ports(new int[] { 7183 }) + .build()) + .tls(TlsOptionsConfig.builder() + .keys(asList("localhost")) + .trust(asList("serverca")) + .sni(asList("mqtt.example.net")) + .alpn(asList("mqtt")) + .trustcacerts(true) + .build()) + .build(); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"specs\":" + + "[" + + "\"mqtt/asyncapi.yaml\"" + + "]," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":7183" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"mqtt.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"mqtt\"" + + "]" + + "}" + + "}")); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java new file mode 100644 index 0000000000..584f972547 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java @@ -0,0 +1,66 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.client; + +import static io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfigurationTest.ASYNCAPI_TARGET_ROUTE_ID_NAME; +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.ScriptProperty; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt") + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config") + .external("mqtt0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.yaml") + @Specification({ + "${asyncapi}/publish.and.subscribe/client", + "${mqtt}/publish.and.subscribe/server" + }) + @Configure(name = ASYNCAPI_TARGET_ROUTE_ID_NAME, value = "4294967298") + @ScriptProperty("serverAddress \"zilla://streams/mqtt0\"") + public void shouldPublishAndSubscribe() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java new file mode 100644 index 0000000000..6a0545109c --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.server; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt") + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config") + .external("asyncapi0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${mqtt}/publish.and.subscribe/client", + "${asyncapi}/publish.and.subscribe/server" + }) + public void shouldPublishAndSubscribe() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/pom.xml b/incubator/pom.xml index faa3f73b59..846204631d 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -18,6 +18,7 @@ binding-amqp.spec + binding-asyncapi.spec catalog-inline.spec catalog-schema-registry.spec exporter-otlp.spec @@ -27,6 +28,7 @@ model-protobuf.spec binding-amqp + binding-asyncapi catalog-inline catalog-schema-registry @@ -51,6 +53,11 @@ binding-amqp ${project.version} + + ${project.groupId} + binding-asyncapi + ${project.version} + ${project.groupId} catalog-inline diff --git a/runtime/binding-grpc/src/test/java/io/aklivity/zilla/runtime/binding/grpc/internal/config/GrpcOptionsConfigAdapterTest.java b/runtime/binding-grpc/src/test/java/io/aklivity/zilla/runtime/binding/grpc/internal/config/GrpcOptionsConfigAdapterTest.java index 288ada9d69..aeb7efe820 100644 --- a/runtime/binding-grpc/src/test/java/io/aklivity/zilla/runtime/binding/grpc/internal/config/GrpcOptionsConfigAdapterTest.java +++ b/runtime/binding-grpc/src/test/java/io/aklivity/zilla/runtime/binding/grpc/internal/config/GrpcOptionsConfigAdapterTest.java @@ -42,8 +42,8 @@ import io.aklivity.zilla.runtime.binding.grpc.config.GrpcProtobufConfig; import io.aklivity.zilla.runtime.binding.grpc.config.GrpcServiceConfig; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; -import io.aklivity.zilla.runtime.engine.internal.config.OptionsAdapter; public class GrpcOptionsConfigAdapterTest { @@ -52,7 +52,7 @@ public class GrpcOptionsConfigAdapterTest @Mock private ConfigAdapterContext context; - private OptionsAdapter adapter; + private OptionsConfigAdapter adapter; private Jsonb jsonb; @@ -67,7 +67,7 @@ public void initJson() throws IOException content = new String(resource.readAllBytes(), UTF_8); } Mockito.doReturn(content).when(context).readURL("protobuf/echo.proto"); - adapter = new OptionsAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); adapter.adaptType("grpc"); JsonbConfig config = new JsonbConfig() .withAdapters(adapter); diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsAdapter.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/OptionsConfigAdapter.java similarity index 84% rename from runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsAdapter.java rename to runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/OptionsConfigAdapter.java index f568acdd32..20bfebc446 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsAdapter.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/OptionsConfigAdapter.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.runtime.engine.internal.config; +package io.aklivity.zilla.runtime.engine.config; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; @@ -25,18 +25,14 @@ import jakarta.json.JsonObject; import jakarta.json.bind.adapter.JsonbAdapter; -import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; -import io.aklivity.zilla.runtime.engine.config.OptionsConfig; -import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; - -public class OptionsAdapter implements JsonbAdapter +public class OptionsConfigAdapter implements JsonbAdapter { private final Map delegatesByName; private ConfigAdapterContext context; private OptionsConfigAdapterSpi delegate; - public OptionsAdapter( + public OptionsConfigAdapter( OptionsConfigAdapterSpi.Kind kind, ConfigAdapterContext context) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/BindingConfigsAdapter.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/BindingConfigsAdapter.java index 6cc76d2de7..74bba3d940 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/BindingConfigsAdapter.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/BindingConfigsAdapter.java @@ -40,6 +40,7 @@ import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; import io.aklivity.zilla.runtime.engine.config.RouteConfig; @@ -57,7 +58,7 @@ public class BindingConfigsAdapter implements JsonbAdapter @@ -32,14 +33,14 @@ public class ExporterAdapter implements JsonbAdapter Date: Thu, 22 Feb 2024 06:54:40 +0530 Subject: [PATCH 08/25] Structured Model require Catalog config (#807) --- .../model/avro/schema/avro.schema.patch.json | 4 ++++ .../model/json/schema/json.schema.patch.json | 8 ++++++++ .../protobuf/schema/protobuf.schema.patch.json | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/incubator/model-avro.spec/src/main/scripts/io/aklivity/zilla/specs/model/avro/schema/avro.schema.patch.json b/incubator/model-avro.spec/src/main/scripts/io/aklivity/zilla/specs/model/avro/schema/avro.schema.patch.json index 7d1a7c526b..0d2cc8dbe6 100644 --- a/incubator/model-avro.spec/src/main/scripts/io/aklivity/zilla/specs/model/avro/schema/avro.schema.patch.json +++ b/incubator/model-avro.spec/src/main/scripts/io/aklivity/zilla/specs/model/avro/schema/avro.schema.patch.json @@ -129,6 +129,10 @@ "maxProperties": 1 } }, + "required": + [ + "catalog" + ], "additionalProperties": false } } diff --git a/incubator/model-json.spec/src/main/scripts/io/aklivity/zilla/specs/model/json/schema/json.schema.patch.json b/incubator/model-json.spec/src/main/scripts/io/aklivity/zilla/specs/model/json/schema/json.schema.patch.json index b9469bc6dc..11bb3f95b3 100644 --- a/incubator/model-json.spec/src/main/scripts/io/aklivity/zilla/specs/model/json/schema/json.schema.patch.json +++ b/incubator/model-json.spec/src/main/scripts/io/aklivity/zilla/specs/model/json/schema/json.schema.patch.json @@ -121,6 +121,10 @@ "maxProperties": 1 } }, + "required": + [ + "catalog" + ], "additionalProperties": false } } @@ -247,6 +251,10 @@ "maxProperties": 1 } }, + "required": + [ + "catalog" + ], "additionalProperties": false } } diff --git a/incubator/model-protobuf.spec/src/main/scripts/io/aklivity/zilla/specs/model/protobuf/schema/protobuf.schema.patch.json b/incubator/model-protobuf.spec/src/main/scripts/io/aklivity/zilla/specs/model/protobuf/schema/protobuf.schema.patch.json index 578800f5d2..b47474ff73 100644 --- a/incubator/model-protobuf.spec/src/main/scripts/io/aklivity/zilla/specs/model/protobuf/schema/protobuf.schema.patch.json +++ b/incubator/model-protobuf.spec/src/main/scripts/io/aklivity/zilla/specs/model/protobuf/schema/protobuf.schema.patch.json @@ -62,7 +62,8 @@ }, "required": [ - "id" + "id", + "record" ], "additionalProperties": false }, @@ -86,7 +87,8 @@ }, "required": [ - "schema" + "schema", + "record" ], "additionalProperties": false }, @@ -110,7 +112,8 @@ }, "required": [ - "strategy" + "strategy", + "record" ], "additionalProperties": false }, @@ -134,7 +137,8 @@ }, "required": [ - "subject" + "subject", + "record" ], "additionalProperties": false } @@ -145,6 +149,10 @@ "maxProperties": 1 } }, + "required": + [ + "catalog" + ], "additionalProperties": false } } From bf6be420542d78f7f7865d06927d750c34629cd2 Mon Sep 17 00:00:00 2001 From: Akram Yakubov Date: Fri, 23 Feb 2024 11:31:56 -0800 Subject: [PATCH 09/25] Support openapi http proxy using openapi.yaml (#778) --- cloud/docker-image/pom.xml | 6 + .../src/main/docker/zpm.json.template | 1 + incubator/binding-openapi.spec/COPYRIGHT | 13 + incubator/binding-openapi.spec/LICENSE | 114 ++ incubator/binding-openapi.spec/NOTICE | 25 + .../binding-openapi.spec/NOTICE.template | 18 + incubator/binding-openapi.spec/mvnw | 310 +++ incubator/binding-openapi.spec/mvnw.cmd | 182 ++ incubator/binding-openapi.spec/pom.xml | 166 ++ .../binding/openapi/OpenapiFunctions.java | 91 + .../specs/binding/openapi/OpenapiSpecs.java | 23 + .../src/main/moditect/module-info.java | 19 + ...kaazing.k3po.lang.el.spi.FunctionMapperSpi | 1 + .../main/resources/META-INF/zilla/openapi.idl | 28 + .../specs/binding/openapi/config/client.yaml | 28 + .../openapi/config/openapi/petstore.yaml | 136 ++ .../binding/openapi/config/server-secure.yaml | 45 + .../specs/binding/openapi/config/server.yaml | 26 + .../openapi/schema/openapi.3.0.schema.json | 1666 +++++++++++++++++ .../openapi/schema/openapi.3.1.schema.json | 1417 ++++++++++++++ .../openapi/schema/openapi.schema.patch.json | 125 ++ .../streams/http/create.pet/client.rpt | 43 + .../streams/http/create.pet/server.rpt | 47 + .../reject.non.composite.origin/client.rpt | 31 + .../reject.non.composite.origin/server.rpt | 23 + .../streams/openapi/create.pet/client.rpt | 50 + .../streams/openapi/create.pet/server.rpt | 54 + .../binding/openapi/config/SchemaTest.java | 52 + .../internal/OpenapiFunctionsTest.java | 65 + .../specs/binding/openapi/streams/HttpIT.java | 59 + .../binding/openapi/streams/OpenapiIT.java | 49 + incubator/binding-openapi/COPYRIGHT | 12 + incubator/binding-openapi/LICENSE | 114 ++ incubator/binding-openapi/NOTICE | 22 + incubator/binding-openapi/NOTICE.template | 13 + incubator/binding-openapi/mvnw | 310 +++ incubator/binding-openapi/mvnw.cmd | 182 ++ incubator/binding-openapi/pom.xml | 293 +++ .../binding/openapi/config/OpenapiConfig.java | 31 + .../openapi/config/OpenapiOptionsConfig.java | 54 + .../config/OpenpaiOptionsConfigBuilder.java | 86 + .../openapi/internal/OpenapiBinding.java | 67 + .../internal/OpenapiBindingContext.java | 77 + .../internal/OpenapiBindingFactorySpi.java | 36 + .../internal/OpenapiConfiguration.java | 41 + .../internal/config/OpenapiBindingConfig.java | 251 +++ .../OpenapiClientCompositeBindingAdapter.java | 255 +++ .../OpenapiCompositeBindingAdapter.java | 56 + .../config/OpenapiOptionsConfigAdapter.java | 267 +++ .../internal/config/OpenapiRouteConfig.java | 39 + .../OpenapiServerCompositeBindingAdapter.java | 495 +++++ .../openapi/internal/model/OpenApi.java | 26 + .../internal/model/OpenApiBearerAuth.java | 20 + .../internal/model/OpenApiComponents.java | 23 + .../openapi/internal/model/OpenApiHeader.java | 20 + .../openapi/internal/model/OpenApiItem.java | 21 + .../internal/model/OpenApiMediaType.java | 20 + .../internal/model/OpenApiOperation.java | 27 + .../internal/model/OpenApiParameter.java | 23 + .../internal/model/OpenApiRequestBody.java | 22 + .../internal/model/OpenApiResponse.java | 20 + .../openapi/internal/model/OpenApiSchema.java | 31 + .../internal/model/OpenApiSecurityScheme.java | 20 + .../openapi/internal/model/OpenApiServer.java | 20 + .../openapi/internal/model/PathItem.java | 27 + .../internal/model/ResponseByContentType.java | 23 + .../streams/OpenapiClientFactory.java | 1066 +++++++++++ .../streams/OpenapiServerFactory.java | 1070 +++++++++++ .../internal/streams/OpenapiState.java | 134 ++ .../streams/OpenapiStreamFactory.java | 27 + .../internal/view/OpenApiOperationView.java | 70 + .../internal/view/OpenApiOperationsView.java | 73 + .../internal/view/OpenApiPathView.java | 65 + .../internal/view/OpenApiResolvable.java | 47 + .../internal/view/OpenApiSchemaView.java | 86 + .../internal/view/OpenApiServerView.java | 41 + .../src/main/moditect/module-info.java | 39 + ...a.runtime.engine.binding.BindingFactorySpi | 1 + ...e.engine.config.CompositeBindingAdapterSpi | 1 + ...time.engine.config.OptionsConfigAdapterSpi | 1 + .../internal/OpenapiConfigurationTest.java | 31 + .../OpenapiOptionsConfigAdapterTest.java | 145 ++ .../internal/streams/OpenapiClientIT.java | 65 + .../internal/streams/OpenapiServerIT.java | 81 + incubator/pom.xml | 7 + .../http/config/HttpAuthorizationConfig.java | 8 + .../HttpAuthorizationConfigBuilder.java | 27 +- .../http/config/HttpOptionsConfigBuilder.java | 2 +- .../http/schema/http.schema.patch.json | 172 +- .../specs/engine/schema/engine.schema.json | 6 + 90 files changed, 11126 insertions(+), 96 deletions(-) create mode 100644 incubator/binding-openapi.spec/COPYRIGHT create mode 100644 incubator/binding-openapi.spec/LICENSE create mode 100644 incubator/binding-openapi.spec/NOTICE create mode 100644 incubator/binding-openapi.spec/NOTICE.template create mode 100755 incubator/binding-openapi.spec/mvnw create mode 100644 incubator/binding-openapi.spec/mvnw.cmd create mode 100644 incubator/binding-openapi.spec/pom.xml create mode 100644 incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java create mode 100644 incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java create mode 100644 incubator/binding-openapi.spec/src/main/moditect/module-info.java create mode 100644 incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi create mode 100644 incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt create mode 100644 incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt create mode 100644 incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java create mode 100644 incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java create mode 100644 incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java create mode 100644 incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java create mode 100644 incubator/binding-openapi/COPYRIGHT create mode 100644 incubator/binding-openapi/LICENSE create mode 100644 incubator/binding-openapi/NOTICE create mode 100644 incubator/binding-openapi/NOTICE.template create mode 100755 incubator/binding-openapi/mvnw create mode 100644 incubator/binding-openapi/mvnw.cmd create mode 100644 incubator/binding-openapi/pom.xml create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java create mode 100644 incubator/binding-openapi/src/main/moditect/module-info.java create mode 100644 incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi create mode 100644 incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi create mode 100644 incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi create mode 100644 incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java create mode 100644 incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java create mode 100644 incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java create mode 100644 incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index 280ead1a1d..a02ca90ede 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -49,6 +49,12 @@ ${project.version} runtime + + ${project.groupId} + binding-openapi + ${project.version} + runtime + ${project.groupId} binding-echo diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template index 4b2d69bc63..db0233de50 100644 --- a/cloud/docker-image/src/main/docker/zpm.json.template +++ b/cloud/docker-image/src/main/docker/zpm.json.template @@ -27,6 +27,7 @@ "io.aklivity.zilla:binding-kafka", "io.aklivity.zilla:binding-mqtt", "io.aklivity.zilla:binding-mqtt-kafka", + "io.aklivity.zilla:binding-openapi", "io.aklivity.zilla:binding-proxy", "io.aklivity.zilla:binding-sse", "io.aklivity.zilla:binding-sse-kafka", diff --git a/incubator/binding-openapi.spec/COPYRIGHT b/incubator/binding-openapi.spec/COPYRIGHT new file mode 100644 index 0000000000..8b1b7215ef --- /dev/null +++ b/incubator/binding-openapi.spec/COPYRIGHT @@ -0,0 +1,13 @@ +Copyright ${copyrightYears} Aklivity Inc. + +Aklivity licenses this file to you under the Apache License, +version 2.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. diff --git a/incubator/binding-openapi.spec/LICENSE b/incubator/binding-openapi.spec/LICENSE new file mode 100644 index 0000000000..1184b83429 --- /dev/null +++ b/incubator/binding-openapi.spec/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. diff --git a/incubator/binding-openapi.spec/NOTICE b/incubator/binding-openapi.spec/NOTICE new file mode 100644 index 0000000000..3936d236bc --- /dev/null +++ b/incubator/binding-openapi.spec/NOTICE @@ -0,0 +1,25 @@ +Licensed under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi.spec/NOTICE.template b/incubator/binding-openapi.spec/NOTICE.template new file mode 100644 index 0000000000..e9ed8f0e7b --- /dev/null +++ b/incubator/binding-openapi.spec/NOTICE.template @@ -0,0 +1,18 @@ +Licensed under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ \ No newline at end of file diff --git a/incubator/binding-openapi.spec/mvnw b/incubator/binding-openapi.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi.spec/mvnw.cmd b/incubator/binding-openapi.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi.spec/pom.xml b/incubator/binding-openapi.spec/pom.xml new file mode 100644 index 0000000000..d1a29c34c9 --- /dev/null +++ b/incubator/binding-openapi.spec/pom.xml @@ -0,0 +1,166 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + binding-openapi.spec + zilla::incubator::binding-openapi.spec + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 11 + 11 + 0.96 + 1 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http openapi + io.aklivity.zilla.specs.binding.openapi.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/binding/openapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java new file mode 100644 index 0000000000..5dca05c51b --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.kaazing.k3po.lang.el.Function; +import org.kaazing.k3po.lang.el.spi.FunctionMapperSpi; + +import io.aklivity.zilla.specs.binding.openapi.internal.types.stream.OpenapiBeginExFW; + +public final class OpenapiFunctions +{ + @Function + public static OpenapiBeginExBuilder beginEx() + { + return new OpenapiBeginExBuilder(); + } + + public static final class OpenapiBeginExBuilder + { + private final OpenapiBeginExFW.Builder beginExRW; + + private OpenapiBeginExBuilder() + { + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1024 * 8]); + this.beginExRW = new OpenapiBeginExFW.Builder().wrap(writeBuffer, 0, writeBuffer.capacity()); + } + + public OpenapiBeginExBuilder typeId( + int typeId) + { + beginExRW.typeId(typeId); + return this; + } + + public OpenapiBeginExBuilder operationId( + String operationId) + { + beginExRW.operationId(operationId); + return this; + } + + public OpenapiBeginExBuilder extension( + byte[] extension) + { + beginExRW.extension(e -> e.set(extension)); + return this; + } + + public byte[] build() + { + final OpenapiBeginExFW beginEx = beginExRW.build(); + final byte[] array = new byte[beginEx.sizeof()]; + beginEx.buffer().getBytes(beginEx.offset(), array); + return array; + } + } + + public static class Mapper extends FunctionMapperSpi.Reflective + { + public Mapper() + { + super(OpenapiFunctions.class); + } + + @Override + public String getPrefixName() + { + return "openapi"; + } + } + + private OpenapiFunctions() + { + // utility + } +} diff --git a/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java new file mode 100644 index 0000000000..3faa1eb489 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi; + +public final class OpenapiSpecs +{ + private OpenapiSpecs() + { + } +} diff --git a/incubator/binding-openapi.spec/src/main/moditect/module-info.java b/incubator/binding-openapi.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..a85321fdd7 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/moditect/module-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +open module io.aklivity.zilla.specs.binding.openapi +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi b/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi new file mode 100644 index 0000000000..cb5c82fd24 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi @@ -0,0 +1 @@ +io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions$Mapper diff --git a/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl b/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl new file mode 100644 index 0000000000..5a24b610ff --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope openapi +{ + + scope stream + { + struct OpenapiBeginEx extends core::stream::Extension + { + int32 apiId = 0; + string16 operationId = null; + octets extension; + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml new file mode 100644 index 0000000000..954302a886 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml @@ -0,0 +1,28 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + openapi0: + type: openapi + kind: client + options: + tcp: + host: localhost + port: 8080 + specs: + - openapi/petstore.yaml diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml new file mode 100644 index 0000000000..8885f2c6b9 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml @@ -0,0 +1,136 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:8080 + - url: https://localhost:9090 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{id}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: id + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml new file mode 100644 index 0000000000..9dd4d71306 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml @@ -0,0 +1,45 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + server: + type: test +bindings: + composite0: + type: openapi + kind: server + vault: server + options: + tls: + keys: + - localhost + alpn: + - protocol2 + http: + authorization: + test0: + credentials: + cookies: + access_token: "{credentials}" + headers: + authorization: Bearer {credentials} + query: + access_token: "{credentials}" + specs: + - openapi/petstore.yaml + exit: openapi0 diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml new file mode 100644 index 0000000000..ce6e1fc0ee --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + composite0: + type: openapi + kind: server + options: + specs: + - openapi/petstore.yaml + exit: openapi0 diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json new file mode 100644 index 0000000000..6a175a4f63 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json @@ -0,0 +1,1666 @@ +{ + "id": "https://spec.openapis.org/oas/3.0/schema/2021-09-28", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "The description of OpenAPI v3.0.x documents, as defined by https://spec.openapis.org/oas/v3.0.3", + "type": "object", + "required": [ + "openapi", + "info", + "paths" + ], + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.0\\.\\d(-.+)?$" + }, + "info": { + "$ref": "#/definitions/Info" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + }, + "uniqueItems": true + }, + "paths": { + "$ref": "#/definitions/Paths" + }, + "components": { + "$ref": "#/definitions/Components" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "definitions": { + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "Info": { + "type": "object", + "required": [ + "title", + "version" + ], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri-reference" + }, + "contact": { + "$ref": "#/definitions/Contact" + }, + "license": { + "$ref": "#/definitions/License" + }, + "version": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Contact": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "License": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Server": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ServerVariable" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ServerVariable": { + "type": "object", + "required": [ + "default" + ], + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Components": { + "type": "object", + "properties": { + "schemas": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "responses": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Response" + } + ] + } + } + }, + "parameters": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Parameter" + } + ] + } + } + }, + "examples": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Example" + } + ] + } + } + }, + "requestBodies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/RequestBody" + } + ] + } + } + }, + "headers": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Header" + } + ] + } + } + }, + "securitySchemes": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/SecurityScheme" + } + ] + } + } + }, + "links": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Link" + } + ] + } + } + }, + "callbacks": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Callback" + } + ] + } + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": { + }, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": { + }, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": { + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Link" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "MediaType": { + "type": "object", + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Encoding" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + } + ] + }, + "Example": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": { + }, + "externalValue": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Header": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + } + ] + }, + "Paths": { + "type": "object", + "patternProperties": { + "^\\/": { + "$ref": "#/definitions/PathItem" + }, + "^x-": { + } + }, + "additionalProperties": false + }, + "PathItem": { + "type": "object", + "properties": { + "$ref": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(get|put|post|delete|options|head|patch|trace)$": { + "$ref": "#/definitions/Operation" + }, + "^x-": { + } + }, + "additionalProperties": false + }, + "Operation": { + "type": "object", + "required": [ + "responses" + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": true + }, + "requestBody": { + "oneOf": [ + { + "$ref": "#/definitions/RequestBody" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "responses": { + "$ref": "#/definitions/Responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Callback" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Responses": { + "type": "object", + "properties": { + "default": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "patternProperties": { + "^[1-5](?:\\d{2}|XX)$": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "^x-": { + } + }, + "minProperties": 1, + "additionalProperties": false + }, + "SecurityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ExampleXORExamples": { + "description": "Example and examples are mutually exclusive", + "not": { + "required": [ + "example", + "examples" + ] + } + }, + "SchemaXORContent": { + "description": "Schema and content are mutually exclusive, at least one is required", + "not": { + "required": [ + "schema", + "content" + ] + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ], + "description": "Some properties are not allowed if content is present", + "allOf": [ + { + "not": { + "required": [ + "style" + ] + } + }, + { + "not": { + "required": [ + "explode" + ] + } + }, + { + "not": { + "required": [ + "allowReserved" + ] + } + }, + { + "not": { + "required": [ + "example" + ] + } + }, + { + "not": { + "required": [ + "examples" + ] + } + } + ] + } + ] + }, + "Parameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "required": [ + "name", + "in" + ], + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + }, + { + "$ref": "#/definitions/ParameterLocation" + } + ] + }, + "ParameterLocation": { + "description": "Parameter location", + "oneOf": [ + { + "description": "Parameter in path", + "required": [ + "required" + ], + "properties": { + "in": { + "enum": [ + "path" + ] + }, + "style": { + "enum": [ + "matrix", + "label", + "simple" + ], + "default": "simple" + }, + "required": { + "enum": [ + true + ] + } + } + }, + { + "description": "Parameter in query", + "properties": { + "in": { + "enum": [ + "query" + ] + }, + "style": { + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ], + "default": "form" + } + } + }, + { + "description": "Parameter in header", + "properties": { + "in": { + "enum": [ + "header" + ] + }, + "style": { + "enum": [ + "simple" + ], + "default": "simple" + } + } + }, + { + "description": "Parameter in cookie", + "properties": { + "in": { + "enum": [ + "cookie" + ] + }, + "style": { + "enum": [ + "form" + ], + "default": "form" + } + } + } + ] + }, + "RequestBody": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "description": { + "type": "string" + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "required": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "SecurityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/APIKeySecurityScheme" + }, + { + "$ref": "#/definitions/HTTPSecurityScheme" + }, + { + "$ref": "#/definitions/OAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/OpenIdConnectSecurityScheme" + } + ] + }, + "APIKeySecurityScheme": { + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "HTTPSecurityScheme": { + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string" + }, + "bearerFormat": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "oneOf": [ + { + "description": "Bearer", + "properties": { + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + } + }, + { + "description": "Non Bearer", + "not": { + "required": [ + "bearerFormat" + ] + }, + "properties": { + "scheme": { + "not": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + } + } + } + ] + }, + "OAuth2SecurityScheme": { + "type": "object", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flows": { + "$ref": "#/definitions/OAuthFlows" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "OpenIdConnectSecurityScheme": { + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "openIdConnect" + ] + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "OAuthFlows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/definitions/ImplicitOAuthFlow" + }, + "password": { + "$ref": "#/definitions/PasswordOAuthFlow" + }, + "clientCredentials": { + "$ref": "#/definitions/ClientCredentialsFlow" + }, + "authorizationCode": { + "$ref": "#/definitions/AuthorizationCodeOAuthFlow" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ImplicitOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "PasswordOAuthFlow": { + "type": "object", + "required": [ + "tokenUrl", + "scopes" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ClientCredentialsFlow": { + "type": "object", + "required": [ + "tokenUrl", + "scopes" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "AuthorizationCodeOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Link": { + "type": "object", + "properties": { + "operationId": { + "type": "string" + }, + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "parameters": { + "type": "object", + "additionalProperties": { + } + }, + "requestBody": { + }, + "description": { + "type": "string" + }, + "server": { + "$ref": "#/definitions/Server" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "not": { + "description": "Operation Id and Operation Ref are mutually exclusive", + "required": [ + "operationId", + "operationRef" + ] + } + }, + "Callback": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PathItem" + }, + "patternProperties": { + "^x-": { + } + } + }, + "Encoding": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "style": { + "type": "string", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json new file mode 100644 index 0000000000..778b2edfa8 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json @@ -0,0 +1,1417 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "The description of OpenAPI v3.1.x documents without schema validation, as defined by https://spec.openapis.org/oas/v3.1.0", + "type": "object", + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.1\\.\\d+(-.+)?$" + }, + "info": { + "$ref": "#/$defs/info" + }, + "jsonSchemaDialect": { + "type": "string", + "format": "uri", + "default": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + }, + "default": [ + { + "url": "/" + } + ] + }, + "paths": { + "$ref": "#/$defs/paths" + }, + "webhooks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "components": { + "$ref": "#/$defs/components" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/$defs/tag" + } + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "openapi", + "info" + ], + "anyOf": [ + { + "required": [ + "paths" + ] + }, + { + "required": [ + "components" + ] + }, + { + "required": [ + "webhooks" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "info": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#info-object", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri" + }, + "contact": { + "$ref": "#/$defs/contact" + }, + "license": { + "$ref": "#/$defs/license" + }, + "version": { + "type": "string" + } + }, + "required": [ + "title", + "version" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "contact": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#contact-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "license": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#license-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "name" + ], + "dependentSchemas": { + "identifier": { + "not": { + "required": [ + "url" + ] + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/server-variable" + } + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server-variable": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-variable-object", + "type": "object", + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "default" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "components": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#components-object", + "type": "object", + "properties": { + "schemas": { + "type": "object", + "additionalProperties": { + "$dynamicRef": "#meta" + } + }, + "responses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/response-or-reference" + } + }, + "parameters": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + }, + "requestBodies": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/request-body-or-reference" + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "securitySchemes": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/security-scheme-or-reference" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "pathItems": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + } + }, + "patternProperties": { + "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": { + "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected", + "propertyNames": { + "pattern": "^[a-zA-Z0-9._-]+$" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "paths": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object", + "type": "object", + "patternProperties": { + "^/": { + "$ref": "#/$defs/path-item" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "get": { + "$ref": "#/$defs/operation" + }, + "put": { + "$ref": "#/$defs/operation" + }, + "post": { + "$ref": "#/$defs/operation" + }, + "delete": { + "$ref": "#/$defs/operation" + }, + "options": { + "$ref": "#/$defs/operation" + }, + "head": { + "$ref": "#/$defs/operation" + }, + "patch": { + "$ref": "#/$defs/operation" + }, + "trace": { + "$ref": "#/$defs/operation" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/path-item" + } + }, + "operation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object", + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "requestBody": { + "$ref": "#/$defs/request-body-or-reference" + }, + "responses": { + "$ref": "#/$defs/responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "external-documentation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#external-documentation-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#parameter-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "path", + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "required": [ + "name", + "in" + ], + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "allowEmptyValue": { + "default": false, + "type": "boolean" + } + } + }, + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/examples" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie" + }, + { + "$ref": "#/$defs/styles-for-form" + } + ], + "$defs": { + "styles-for-path": { + "if": { + "properties": { + "in": { + "const": "path" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "enum": [ + "matrix", + "label", + "simple" + ] + }, + "required": { + "const": true + } + }, + "required": [ + "required" + ] + } + }, + "styles-for-header": { + "if": { + "properties": { + "in": { + "const": "header" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + } + } + } + }, + "styles-for-query": { + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + } + } + }, + "styles-for-cookie": { + "if": { + "properties": { + "in": { + "const": "cookie" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "const": "form" + } + } + } + } + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/parameter" + } + }, + "request-body": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#request-body-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "content": { + "$ref": "#/$defs/content" + }, + "required": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "request-body-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/request-body" + } + }, + "content": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#fixed-fields-10", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + }, + "propertyNames": { + "format": "media-range" + } + }, + "media-type": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#media-type-object", + "type": "object", + "properties": { + "schema": { + "$dynamicRef": "#meta" + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/encoding" + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/examples" + } + ], + "unevaluatedProperties": false + }, + "encoding": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#encoding-object", + "type": "object", + "properties": { + "contentType": { + "type": "string", + "format": "media-range" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/styles-for-form" + } + ], + "unevaluatedProperties": false + }, + "responses": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#responses-object", + "type": "object", + "properties": { + "default": { + "$ref": "#/$defs/response-or-reference" + } + }, + "patternProperties": { + "^[1-5](?:[0-9]{2}|XX)$": { + "$ref": "#/$defs/response-or-reference" + } + }, + "minProperties": 1, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "if": { + "$comment": "either default, or at least one response code property must exist", + "patternProperties": { + "^[1-5](?:[0-9]{2}|XX)$": false + } + }, + "then" : { + "required": [ "default" ] + } + }, + "response": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#response-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "content": { + "$ref": "#/$defs/content" + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + } + }, + "required": [ + "description" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "response-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/response" + } + }, + "callbacks": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object", + "type": "object", + "$ref": "#/$defs/specification-extensions", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "callbacks-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/callbacks" + } + }, + "example": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#example-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": true, + "externalValue": { + "type": "string", + "format": "uri" + } + }, + "not": { + "required": [ + "value", + "externalValue" + ] + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "example-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/example" + } + }, + "link": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#link-object", + "type": "object", + "properties": { + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "$ref": "#/$defs/map-of-strings" + }, + "requestBody": true, + "description": { + "type": "string" + }, + "body": { + "$ref": "#/$defs/server" + } + }, + "oneOf": [ + { + "required": [ + "operationRef" + ] + }, + { + "required": [ + "operationId" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "link-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/link" + } + }, + "header": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#header-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + }, + "explode": { + "default": false, + "type": "boolean" + } + }, + "$ref": "#/$defs/examples" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "header-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/header" + } + }, + "tag": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#tag-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "name" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "reference": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#reference-object", + "type": "object", + "properties": { + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "schema": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object", + "$dynamicAnchor": "meta", + "type": [ + "object", + "boolean" + ] + }, + "security-scheme": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-scheme-object", + "type": "object", + "properties": { + "type": { + "enum": [ + "apiKey", + "http", + "mutualTLS", + "oauth2", + "openIdConnect" + ] + }, + "description": { + "type": "string" + } + }, + "required": [ + "type" + ], + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-apikey" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http-bearer" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oauth2" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oidc" + } + ], + "unevaluatedProperties": false, + "$defs": { + "type-apikey": { + "if": { + "properties": { + "type": { + "const": "apiKey" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "cookie" + ] + } + }, + "required": [ + "name", + "in" + ] + } + }, + "type-http": { + "if": { + "properties": { + "type": { + "const": "http" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "scheme": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + } + }, + "type-http-bearer": { + "if": { + "properties": { + "type": { + "const": "http" + }, + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + }, + "required": [ + "type", + "scheme" + ] + }, + "then": { + "properties": { + "bearerFormat": { + "type": "string" + } + } + } + }, + "type-oauth2": { + "if": { + "properties": { + "type": { + "const": "oauth2" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "flows": { + "$ref": "#/$defs/oauth-flows" + } + }, + "required": [ + "flows" + ] + } + }, + "type-oidc": { + "if": { + "properties": { + "type": { + "const": "openIdConnect" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "openIdConnectUrl": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "openIdConnectUrl" + ] + } + } + } + }, + "security-scheme-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/security-scheme" + } + }, + "oauth-flows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/$defs/oauth-flows/$defs/implicit" + }, + "password": { + "$ref": "#/$defs/oauth-flows/$defs/password" + }, + "clientCredentials": { + "$ref": "#/$defs/oauth-flows/$defs/client-credentials" + }, + "authorizationCode": { + "$ref": "#/$defs/oauth-flows/$defs/authorization-code" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "implicit": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "password": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "client-credentials": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "authorization-code": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + } + } + }, + "security-requirement": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-requirement-object", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "specification-extensions": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#specification-extensions", + "patternProperties": { + "^x-": true + } + }, + "examples": { + "properties": { + "example": true, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + } + } + }, + "map-of-strings": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "styles-for-form": { + "if": { + "properties": { + "style": { + "const": "form" + } + }, + "required": [ + "style" + ] + }, + "then": { + "properties": { + "explode": { + "default": true + } + } + }, + "else": { + "properties": { + "explode": { + "default": false + } + } + } + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json new file mode 100644 index 0000000000..8f7161754c --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json @@ -0,0 +1,125 @@ +[ + { + "op": "add", + "path": "/$defs/binding/properties/type/enum/-", + "value": "openapi" + }, + { + "op": "add", + "path": "/$defs/binding/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "openapi" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "openapi" + }, + "kind": + { + "enum": [ "client", "server" ] + }, + "options": + { + "properties": + { + "tcp": "#/$defs/binding/tcp/options", + "tls": "#/$defs/binding/tls/options", + "http": + { + "title": "Http", + "type": "object", + "properties": + { + "authorization": "$defs/options/binding/http/authorization" + }, + "additionalProperties": false + }, + "specs": + { + "title": "Specs", + "type": "array", + "items": + { + "title": "Specs", + "type": "string" + } + } + }, + "additionalProperties": false + }, + "routes": false + }, + "oneOf": + [ + { + "properties": + { + "kind": + { + "const": "server" + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "properties": + { + "routes": + { + "required": + [ + "exit" + ] + } + }, + "required": + [ + "routes" + ] + } + ] + }, + { + "properties": + { + "kind": + { + "const": "client" + }, + "routes": + { + "items": + { + "properties": + { + "exit": false + } + } + }, + "exit": false + } + } + ] + } + } + } +] diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt new file mode 100644 index 0000000000..8e97310066 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/http" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} +connected + +write "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()} + +read "{\"code\": 0,\"message\": \"string\"}" diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt new file mode 100644 index 0000000000..d7a53f172b --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} + +connected + +read "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()} + +write "{\"code\": 0,\"message\": \"string\"}" +write flush diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt new file mode 100644 index 0000000000..ce2f2bb07f --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt @@ -0,0 +1,31 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/http" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} +connect aborted diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt new file mode 100644 index 0000000000..c741d4e588 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt @@ -0,0 +1,23 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 12 + option zilla:transmission "half-duplex" + +rejected diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt new file mode 100644 index 0000000000..11a25bae39 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt @@ -0,0 +1,50 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/openapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} +connected + +write "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +write close + +read zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()) + .build()} + +read "{\"code\": 0,\"message\": \"string\"}" diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt new file mode 100644 index 0000000000..c074450d95 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt @@ -0,0 +1,54 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/openapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} + +connected + +read "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +read closed + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()) + .build()} +write flush + +write "{\"code\": 0,\"message\": \"string\"}" +write flush diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java new file mode 100644 index 0000000000..4fdfa3e4c4 --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.JsonObject; + +import org.junit.Rule; +import org.junit.Test; + +import io.aklivity.zilla.specs.engine.config.ConfigSchemaRule; + +public class SchemaTest +{ + @Rule + public final ConfigSchemaRule schema = new ConfigSchemaRule() + .schemaPatch("io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json") + .schemaPatch("io/aklivity/zilla/specs/engine/schema/vault/test.schema.patch.json") + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config"); + + @Test + public void shouldValidateServer() + { + JsonObject config = schema.validate("server.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateClient() + { + JsonObject config = schema.validate("client.yaml"); + + assertThat(config, not(nullValue())); + } +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java new file mode 100644 index 0000000000..ef2ffec766 --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import java.lang.reflect.Method; + +import javax.el.ELContext; +import javax.el.FunctionMapper; + +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; +import org.kaazing.k3po.lang.internal.el.ExpressionContext; + +import io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions; +import io.aklivity.zilla.specs.binding.openapi.internal.types.stream.OpenapiBeginExFW; + + +public class OpenapiFunctionsTest +{ + @Test + public void shouldResolveFunction() throws Exception + { + final ELContext ctx = new ExpressionContext(); + final FunctionMapper mapper = ctx.getFunctionMapper(); + final Method function = mapper.resolveFunction("openapi", "beginEx"); + + assertNotNull(function); + assertSame(OpenapiFunctions.class, function.getDeclaringClass()); + } + + @Test + public void shouldGenerateBeginExtension() + { + byte[] build = OpenapiFunctions.beginEx() + .typeId(0x01) + .operationId("test") + .extension("extension".getBytes()) + .build(); + + DirectBuffer buffer = new UnsafeBuffer(build); + + OpenapiBeginExFW beginEx = new OpenapiBeginExFW().wrap(buffer, 0, buffer.capacity()); + assertEquals(0L, beginEx.apiId()); + assertEquals("test", beginEx.operationId().asString()); + assertEquals("extension".length(), beginEx.extension().sizeof()); + } +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java new file mode 100644 index 0000000000..2595d2c2ca --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class HttpIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${http}/create.pet/client", + "${http}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${http}/reject.non.composite.origin/client", + "${http}/reject.non.composite.origin/server" + }) + public void shouldRejectNonCompositeOrigin() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java new file mode 100644 index 0000000000..66b019bc53 --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class OpenapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${openapi}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi/COPYRIGHT b/incubator/binding-openapi/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-openapi/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-openapi/LICENSE b/incubator/binding-openapi/LICENSE new file mode 100644 index 0000000000..f6abb6327b --- /dev/null +++ b/incubator/binding-openapi/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/binding-openapi/NOTICE b/incubator/binding-openapi/NOTICE new file mode 100644 index 0000000000..81c0ad0dcf --- /dev/null +++ b/incubator/binding-openapi/NOTICE @@ -0,0 +1,22 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::incubator::catalog-inline under Aklivity Community License Agreement + zilla::incubator::model-core under Aklivity Community License Agreement + zilla::incubator::model-json under Aklivity Community License Agreement + zilla::runtime::binding-http under The Apache Software License, Version 2.0 + zilla::runtime::binding-tcp under The Apache Software License, Version 2.0 + zilla::runtime::binding-tls under The Apache Software License, Version 2.0 + diff --git a/incubator/binding-openapi/NOTICE.template b/incubator/binding-openapi/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/binding-openapi/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/binding-openapi/mvnw b/incubator/binding-openapi/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi/mvnw.cmd b/incubator/binding-openapi/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi/pom.xml b/incubator/binding-openapi/pom.xml new file mode 100644 index 0000000000..0d4ce30338 --- /dev/null +++ b/incubator/binding-openapi/pom.xml @@ -0,0 +1,293 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + binding-openapi + zilla::incubator::binding-openapi + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.60 + 4 + + + + + ${project.groupId} + binding-openapi.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + io.aklivity.zilla + binding-http + ${project.version} + + + io.aklivity.zilla + binding-tcp + ${project.version} + + + io.aklivity.zilla + binding-tls + ${project.version} + + + io.aklivity.zilla + catalog-inline + ${project.version} + + + io.aklivity.zilla + model-core + ${project.version} + + + io.aklivity.zilla + model-json + ${project.version} + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http openapi + io.aklivity.zilla.runtime.binding.openapi.internal.types + + + + + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + binding-openapi.spec + + + ^\Qio/aklivity/zilla/specs/binding/openapi/\E + io/aklivity/zilla/runtime/binding/openapi/internal/ + + + + + io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json, + io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json, + io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json + + ${project.build.directory}/classes + + + + unpack-openapi + generate-sources + + unpack + + + + + ${project.groupId} + binding-openapi.spec + ${project.version} + ${basedir}/target/test-classes + **\/*.yaml + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/binding/openapi/internal/types/**/*.class + io/aklivity/zilla/runtime/binding/openapi/internal/model/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java new file mode 100644 index 0000000000..dd2239e5e8 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; + +public class OpenapiConfig +{ + public final String location; + public final OpenApi openapi; + + public OpenapiConfig( + String location, + OpenApi openapi) + { + this.location = location; + this.openapi = openapi; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java new file mode 100644 index 0000000000..1846572096 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class OpenapiOptionsConfig extends OptionsConfig +{ + public final TcpOptionsConfig tcp; + public final TlsOptionsConfig tls; + public final HttpOptionsConfig http; + public final List openapis; + + public static OpenpaiOptionsConfigBuilder builder() + { + return new OpenpaiOptionsConfigBuilder<>(OpenapiOptionsConfig.class::cast); + } + + public static OpenpaiOptionsConfigBuilder builder( + Function mapper) + { + return new OpenpaiOptionsConfigBuilder<>(mapper); + } + + public OpenapiOptionsConfig( + TcpOptionsConfig tcp, + TlsOptionsConfig tls, + HttpOptionsConfig http, + List openapis) + { + this.tcp = tcp; + this.tls = tls; + this.http = http; + this.openapis = openapis; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java new file mode 100644 index 0000000000..b3ce2449f6 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class OpenpaiOptionsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private TcpOptionsConfig tcp; + private TlsOptionsConfig tls; + private HttpOptionsConfig http; + private List openapis; + + OpenpaiOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public OpenpaiOptionsConfigBuilder tcp( + TcpOptionsConfig tcp) + { + this.tcp = tcp; + return this; + } + + public OpenpaiOptionsConfigBuilder tls( + TlsOptionsConfig tls) + { + this.tls = tls; + return this; + } + + public OpenpaiOptionsConfigBuilder http( + HttpOptionsConfig http) + { + this.http = http; + return this; + } + + public OpenpaiOptionsConfigBuilder openapi( + OpenapiConfig openapi) + { + if (openapis == null) + { + openapis = new ArrayList<>(); + } + openapis.add(openapi); + return this; + } + + @Override + public T build() + { + return mapper.apply(new OpenapiOptionsConfig(tcp, tls, http, openapis)); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java new file mode 100644 index 0000000000..8c9a3f9bd1 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class OpenapiBinding implements Binding +{ + public static final String NAME = "openapi"; + + private final OpenapiConfiguration config; + + OpenapiBinding( + OpenapiConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/openapi.schema.patch.json"); + } + + @Override + public String originType( + KindConfig kind) + { + return kind == KindConfig.CLIENT ? NAME : null; + } + + @Override + public String routedType( + KindConfig kind) + { + return kind == KindConfig.SERVER ? NAME : null; + } + + @Override + public OpenapiBindingContext supply( + EngineContext context) + { + return new OpenapiBindingContext(config, context); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java new file mode 100644 index 0000000000..048c400107 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import java.util.EnumMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiClientFactory; +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiServerFactory; +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiStreamFactory; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +final class OpenapiBindingContext implements BindingContext +{ + private final Map factories; + + OpenapiBindingContext( + OpenapiConfiguration config, + EngineContext context) + { + Map factories = new EnumMap<>(KindConfig.class); + factories.put(SERVER, new OpenapiServerFactory(config, context)); + factories.put(CLIENT, new OpenapiClientFactory(config, context)); + this.factories = factories; + } + + @Override + public BindingHandler attach( + BindingConfig binding) + { + OpenapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.attach(binding); + } + + return factory; + } + + @Override + public void detach( + BindingConfig binding) + { + OpenapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.detach(binding.id); + } + } + + @Override + public String toString() + { + return String.format("%s %s", getClass().getSimpleName(), factories); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java new file mode 100644 index 0000000000..d587d85f5a --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import io.aklivity.zilla.runtime.common.feature.Incubating; +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi; + +@Incubating +public final class OpenapiBindingFactorySpi implements BindingFactorySpi +{ + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public OpenapiBinding create( + Configuration config) + { + return new OpenapiBinding(new OpenapiConfiguration(config)); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java new file mode 100644 index 0000000000..d745d35d2c --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class OpenapiConfiguration extends Configuration +{ + public static final LongPropertyDef OPENAPI_TARGET_ROUTE_ID; + private static final ConfigurationDef OPENAPI_CONFIG; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.binding.openapi"); + OPENAPI_TARGET_ROUTE_ID = config.property("target.route.id", -1L); + OPENAPI_CONFIG = config; + } + + public OpenapiConfiguration( + Configuration config) + { + super(OPENAPI_CONFIG, config); + } + + public long targetRouteId() + { + return OPENAPI_TARGET_ROUTE_ID.getAsLong(this); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java new file mode 100644 index 0000000000..58c5fae230 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java @@ -0,0 +1,251 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.util.Collections.unmodifiableMap; +import static java.util.stream.Collector.of; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.agrona.AsciiSequenceView; +import org.agrona.DirectBuffer; +import org.agrona.collections.IntHashSet; +import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.String16FW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiBindingConfig +{ + public final long id; + public final String name; + public final KindConfig kind; + public final OpenapiOptionsConfig options; + public final List routes; + public final HttpHeaderHelper helper; + + private final long overrideRouteId; + private final IntHashSet httpOrigins; + private final Long2LongHashMap resolvedIds; + private final Object2ObjectHashMap paths; + private final Map> resolversByMethod; + + public OpenapiBindingConfig( + BindingConfig binding, + long overrideRouteId) + { + this.id = binding.id; + this.name = binding.name; + this.kind = binding.kind; + this.overrideRouteId = overrideRouteId; + this.options = OpenapiOptionsConfig.class.cast(binding.options); + this.paths = new Object2ObjectHashMap<>(); + options.openapis.forEach(c -> c.openapi.paths.forEach((k, v) -> + { + String regex = k.replaceAll("\\{[^/]+}", "[^/]+"); + regex = "^" + regex + "$"; + Pattern pattern = Pattern.compile(regex); + paths.put(pattern.matcher(""), v); + })); + + this.routes = binding.routes.stream().map(OpenapiRouteConfig::new).collect(toList()); + + this.resolvedIds = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http")) + .collect(of( + () -> new Long2LongHashMap(-1), + (m, r) -> m.put(0L, r.id), //TODO: populate proper apiId + (m, r) -> m, + IDENTITY_FINISH + )); + + this.httpOrigins = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http")) + .map(b -> NamespacedId.namespaceId(b.id)) + .collect(toCollection(IntHashSet::new)); + this.helper = new HttpHeaderHelper(); + + Map> resolversByMethod = new TreeMap<>(CharSequence::compare); + resolversByMethod.put("POST", o -> o.post != null ? o.post.operationId : null); + resolversByMethod.put("PUT", o -> o.put != null ? o.put.operationId : null); + resolversByMethod.put("GET", o -> o.get != null ? o.get.operationId : null); + resolversByMethod.put("DELETE", o -> o.delete != null ? o.delete.operationId : null); + resolversByMethod.put("OPTIONS", o -> o.options != null ? o.options.operationId : null); + resolversByMethod.put("HEAD", o -> o.head != null ? o.head.operationId : null); + resolversByMethod.put("PATCH", o -> o.patch != null ? o.patch.operationId : null); + resolversByMethod.put("TRACE", o -> o.post != null ? o.trace.operationId : null); + this.resolversByMethod = unmodifiableMap(resolversByMethod); + } + + public boolean isCompositeNamespace( + int namespaceId) + { + return httpOrigins.contains(namespaceId); + } + + public long resolveResolvedId( + long apiId) + { + return overrideRouteId != -1 ? overrideRouteId : resolvedIds.get(apiId); + } + + public String resolveOperationId( + HttpBeginExFW httpBeginEx) + { + helper.visit(httpBeginEx); + + String operationId = null; + + resolve: + for (Map.Entry item : paths.entrySet()) + { + Matcher matcher = item.getKey(); + matcher.reset(helper.path); + if (matcher.find()) + { + PathItem operations = item.getValue(); + operationId = resolveMethod(operations); + break resolve; + } + } + + return operationId; + } + + private String resolveMethod( + PathItem operations) + { + Function resolver = resolversByMethod.get(helper.method); + return resolver != null ? resolver.apply(operations) : null; + } + + public OpenapiRouteConfig resolve( + long authorization) + { + return routes.stream() + .filter(r -> r.authorized(authorization)) + .findFirst() + .orElse(null); + } + + private static final class HttpHeaderHelper + { + private static final String8FW HEADER_NAME_METHOD = new String8FW(":method"); + private static final String8FW HEADER_NAME_PATH = new String8FW(":path"); + private static final String8FW HEADER_NAME_SCHEME = new String8FW(":scheme"); + private static final String8FW HEADER_NAME_AUTHORITY = new String8FW(":authority"); + + private final Map> visitors; + { + Map> visitors = new HashMap<>(); + visitors.put(HEADER_NAME_METHOD, this::visitMethod); + visitors.put(HEADER_NAME_PATH, this::visitPath); + visitors.put(HEADER_NAME_SCHEME, this::visitScheme); + visitors.put(HEADER_NAME_AUTHORITY, this::visitAuthority); + this.visitors = visitors; + } + private final AsciiSequenceView methodRO = new AsciiSequenceView(); + private final AsciiSequenceView pathRO = new AsciiSequenceView(); + private final String16FW schemeRO = new String16FW(); + private final String16FW authorityRO = new String16FW(); + + public CharSequence path; + public CharSequence method; + public String16FW scheme; + public String16FW authority; + + private void visit( + HttpBeginExFW beginEx) + { + method = null; + path = null; + scheme = null; + authority = null; + + if (beginEx != null) + { + beginEx.headers().forEach(this::dispatch); + } + } + + private boolean dispatch( + HttpHeaderFW header) + { + final String8FW name = header.name(); + final Consumer visitor = visitors.get(name); + if (visitor != null) + { + visitor.accept(header.value()); + } + + return method != null && + path != null && + scheme != null && + authority != null; + } + + private void visitMethod( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + method = methodRO.wrap(buffer, offset, length); + } + + private void visitPath( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + path = pathRO.wrap(buffer, offset, length); + } + + private void visitScheme( + String16FW value) + { + scheme = schemeRO.wrap(value.buffer(), value.offset(), value.limit()); + } + + private void visitAuthority( + String16FW value) + { + authority = authorityRO.wrap(value.buffer(), value.offset(), value.limit()); + } + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java new file mode 100644 index 0000000000..7d1f47c21b --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java @@ -0,0 +1,255 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static java.util.Objects.requireNonNull; + +import java.net.URI; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpResponseConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiHeader; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiResponse; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.ResponseByContentType; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiOperationView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiOperationsView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiSchemaView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiServerView; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.model.core.config.IntegerModelConfig; +import io.aklivity.zilla.runtime.model.core.config.StringModelConfig; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public final class OpenapiClientCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final String INLINE_CATALOG_NAME = "catalog0"; + + private final Map models = Map.of( + "string", StringModelConfig.builder().build(), + "integer", IntegerModelConfig.builder().build() + ); + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + public OpenapiClientCompositeBindingAdapter() + { + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options; + OpenapiConfig openapiConfig = options.openapis.get(0); + + final OpenApi openApi = openapiConfig.openapi; + final int[] httpsPorts = resolvePortsForScheme(openApi, "https"); + final boolean secure = httpsPorts != null; + + return BindingConfig.builder(binding) + .composite() + .name(String.format(binding.qname, "$composite")) + .binding() + .name("http_client0") + .type("http") + .kind(CLIENT) + .inject(b -> this.injectHttpClientOptions(b, openApi)) + .exit(secure ? "tls_client0" : "tcp_client0") + .build() + .inject(b -> this.injectTlsClient(b, options.tls, secure)) + .binding() + .name("tcp_client0") + .type("tcp") + .kind(CLIENT) + .options(options.tcp) + .build() + .build() + .build(); + } + + private int[] resolvePortsForScheme( + OpenApi openApi, + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(openApi, scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + private URI findFirstServerUrlWithScheme( + OpenApi openApi, + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (OpenApiServer item : openApi.servers) + { + OpenApiServerView server = OpenApiServerView.of(item); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } + + private BindingConfigBuilder injectHttpClientOptions( + BindingConfigBuilder binding, + OpenApi openApi) + { + OpenApiOperationsView operations = OpenApiOperationsView.of(openApi.paths); + if (operations.hasResponses()) + { + binding. + options(HttpOptionsConfig::builder) + .inject(options -> injectHttpClientRequests(operations, options, openApi)) + .build(); + } + return binding; + } + + private HttpOptionsConfigBuilder injectHttpClientRequests( + OpenApiOperationsView operations, + HttpOptionsConfigBuilder options, + OpenApi openApi) + { + for (String pathName : openApi.paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + OpenApiOperationView operation = operations.operation(pathName, methodName); + if (operation.hasResponses()) + { + options + .request() + .path(pathName) + .method(HttpRequestConfig.Method.valueOf(methodName)) + .inject(request -> injectResponses(request, operation, openApi)) + .build() + .build(); + } + } + } + return options; + } + + private HttpRequestConfigBuilder injectResponses( + HttpRequestConfigBuilder request, + OpenApiOperationView operation, + OpenApi openApi) + { + if (operation != null && operation.responsesByStatus() != null) + { + for (Map.Entry responses0 : operation.responsesByStatus().entrySet()) + { + String status = responses0.getKey(); + ResponseByContentType responses1 = responses0.getValue(); + if (!(OpenApiOperationView.DEFAULT.equals(status)) && responses1.content != null) + { + for (Map.Entry response2 : responses1.content.entrySet()) + { + OpenApiSchemaView schema = OpenApiSchemaView.of(openApi.components.schemas, response2.getValue().schema); + request + .response() + .status(Integer.parseInt(status)) + .contentType(response2.getKey()) + .inject(response -> injectResponseHeaders(responses1, response)) + .content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .schema() + .subject(schema.refKey()) + .build() + .build() + .build() + .build(); + } + } + } + } + return request; + } + + private HttpResponseConfigBuilder injectResponseHeaders( + ResponseByContentType responses, + HttpResponseConfigBuilder response) + { + if (responses.headers != null && !responses.headers.isEmpty()) + { + for (Map.Entry header : responses.headers.entrySet()) + { + String name = header.getKey(); + ModelConfig model = models.get(header.getValue().schema.type); + if (model != null) + { + response + .header() + .name(name) + .model(model) + .build(); + } + } + } + return response; + } + + private NamespaceConfigBuilder injectTlsClient( + NamespaceConfigBuilder namespace, + TlsOptionsConfig tlsConfig, + boolean secure) + { + if (secure) + { + namespace + .binding() + .name("tls_client0") + .type("tls") + .kind(CLIENT) + .options(tlsConfig) + .vault("client") + .exit("tcp_client0") + .build(); + } + return namespace; + } + +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java new file mode 100644 index 0000000000..06ad49a40c --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; +import static java.util.function.UnaryOperator.identity; + +import java.util.EnumMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class OpenapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private final UnaryOperator composite; + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + public OpenapiCompositeBindingAdapter() + { + Map> composites = new EnumMap<>(KindConfig.class); + composites.put(SERVER, new OpenapiServerCompositeBindingAdapter()::adapt); + composites.put(CLIENT, new OpenapiClientCompositeBindingAdapter()::adapt); + UnaryOperator composite = binding -> composites + .getOrDefault(binding.kind, identity()).apply(binding); + this.composite = composite; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + return composite.apply(binding); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java new file mode 100644 index 0000000000..c967db887f --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java @@ -0,0 +1,267 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.util.Collections.unmodifiableMap; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.InputStream; +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonReader; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import org.agrona.collections.Object2ObjectHashMap; +import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.JsonValidationService; +import org.leadpony.justify.api.ProblemHandler; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenpaiOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.ConfigException; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public final class OpenapiOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + private static final String TCP_NAME = "tcp"; + private static final String TLS_NAME = "tls"; + private static final String HTTP_NAME = "http"; + private static final String SPECS_NAME = "specs"; + + private final Map schemas; + + private OptionsConfigAdapter tcpOptions; + private OptionsConfigAdapter tlsOptions; + private OptionsConfigAdapter httpOptions; + private Function readURL; + + public OpenapiOptionsConfigAdapter() + { + Map schemas = new Object2ObjectHashMap<>(); + schemas.put("3.0.0", schema("3.0.0")); + schemas.put("3.1.0", schema("3.1.0")); + this.schemas = unmodifiableMap(schemas); + } + + @Override + public Kind kind() + { + return Kind.BINDING; + } + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + OpenapiOptionsConfig openOptions = (OpenapiOptionsConfig) options; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (openOptions.tcp != null) + { + final TcpOptionsConfig tcp = ((OpenapiOptionsConfig) options).tcp; + object.add(TCP_NAME, tcpOptions.adaptToJson(tcp)); + } + + if (openOptions.tls != null) + { + final TlsOptionsConfig tls = ((OpenapiOptionsConfig) options).tls; + object.add(TLS_NAME, tlsOptions.adaptToJson(tls)); + } + + HttpOptionsConfig http = openOptions.http; + if (http != null) + { + object.add(HTTP_NAME, httpOptions.adaptToJson(http)); + } + + if (openOptions.openapis != null) + { + JsonArrayBuilder keys = Json.createArrayBuilder(); + openOptions.openapis.forEach(p -> keys.add(p.location)); + object.add(SPECS_NAME, keys); + } + + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + OpenpaiOptionsConfigBuilder openapiOptions = OpenapiOptionsConfig.builder(); + + if (object.containsKey(TCP_NAME)) + { + final JsonObject tcp = object.getJsonObject(TCP_NAME); + final TcpOptionsConfig tcpOptions = (TcpOptionsConfig) this.tcpOptions.adaptFromJson(tcp); + openapiOptions.tcp(tcpOptions); + } + + if (object.containsKey(TLS_NAME)) + { + final JsonObject tls = object.getJsonObject(TLS_NAME); + final TlsOptionsConfig tlsOptions = (TlsOptionsConfig) this.tlsOptions.adaptFromJson(tls); + openapiOptions.tls(tlsOptions); + } + + if (object.containsKey(HTTP_NAME)) + { + JsonObject http = object.getJsonObject(HTTP_NAME); + + final HttpOptionsConfig httpOptions = (HttpOptionsConfig) this.httpOptions.adaptFromJson(http); + openapiOptions.http(httpOptions); + } + + if (object.containsKey(SPECS_NAME)) + { + object.getJsonArray(SPECS_NAME).forEach(s -> openapiOptions.openapi(asOpenapi(s))); + } + + return openapiOptions.build(); + } + + @Override + public void adaptContext( + ConfigAdapterContext context) + { + this.readURL = context::readURL; + this.tcpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tcpOptions.adaptType("tcp"); + this.tlsOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tlsOptions.adaptType("tls"); + this.httpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.httpOptions.adaptType("http"); + } + + private OpenapiConfig asOpenapi( + JsonValue value) + { + final String location = ((JsonString) value).getString(); + final String specText = readURL.apply(location); + OpenApi openapi = parseOpenApi(specText); + + return new OpenapiConfig(location, openapi); + } + + private OpenApi parseOpenApi( + String openapiText) + { + OpenApi openApi = null; + + List errors = new LinkedList<>(); + + try + { + String openApiVersion = detectOpenApiVersion(openapiText); + + JsonValidationService service = JsonValidationService.newInstance(); + ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); + JsonSchema schema = schemas.get(openApiVersion); + + service.createReader(new StringReader(openapiText), schema, handler).read(); + + Jsonb jsonb = JsonbBuilder.create(); + + openApi = jsonb.fromJson(openapiText, OpenApi.class); + } + catch (Exception ex) + { + errors.add(ex); + } + + if (!errors.isEmpty()) + { + Exception ex = errors.remove(0); + errors.forEach(ex::addSuppressed); + rethrowUnchecked(ex); + } + + return openApi; + } + + private String detectOpenApiVersion( + String openapiText) + { + try (JsonReader reader = Json.createReader(new StringReader(openapiText))) + { + JsonObject json = reader.readObject(); + if (json.containsKey("openapi")) + { + return json.getString("openapi"); + } + else + { + throw new IllegalArgumentException("Unable to determine OpenAPI version."); + } + } + catch (Exception e) + { + throw new RuntimeException("Error reading OpenAPI document.", e); + } + } + + private JsonSchema schema( + String version) + { + InputStream schemaInput = null; + boolean detect = true; + + if (version.startsWith("3.0")) + { + schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.0.schema.json"); + } + else if (version.startsWith("3.1")) + { + schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.1.schema.json"); + detect = false; + } + + JsonValidationService service = JsonValidationService.newInstance(); + + return service.createSchemaReaderFactoryBuilder() + .withSpecVersionDetection(detect) + .build() + .createSchemaReader(schemaInput) + .read(); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java new file mode 100644 index 0000000000..82b092dc23 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import java.util.function.LongPredicate; + +import io.aklivity.zilla.runtime.engine.config.RouteConfig; + +public final class OpenapiRouteConfig +{ + public final long id; + + private final LongPredicate authorized; + + public OpenapiRouteConfig( + RouteConfig route) + { + this.id = route.id; + this.authorized = route.authorized; + } + + boolean authorized( + long authorization) + { + return authorized.test(authorization); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java new file mode 100644 index 0000000000..1cc86feda2 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java @@ -0,0 +1,495 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.binding.http.config.HttpPolicyConfig.CROSS_ORIGIN; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; +import static java.util.Objects.requireNonNull; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.http.config.HttpAuthorizationConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpConditionConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiMediaType; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiParameter; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiSchema; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiSchemaView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiServerView; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpConditionConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineOptionsConfig; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineSchemaConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.GuardedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; +import io.aklivity.zilla.runtime.model.core.config.IntegerModelConfig; +import io.aklivity.zilla.runtime.model.core.config.StringModelConfig; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public final class OpenapiServerCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final String INLINE_CATALOG_NAME = "catalog0"; + private static final String INLINE_CATALOG_TYPE = "inline"; + private static final String VERSION_LATEST = "latest"; + private static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + + private final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); + private final Map models = Map.of( + "string", StringModelConfig.builder().build(), + "integer", IntegerModelConfig.builder().build() + ); + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options; + OpenapiConfig openapiConfig = options.openapis.get(0); + + final OpenApi openApi = openapiConfig.openapi; + final TlsOptionsConfig tlsOption = options.tls != null ? options.tls : null; + final HttpOptionsConfig httpOptions = options.http; + final String guardName = httpOptions != null ? httpOptions.authorization.name : null; + final HttpAuthorizationConfig authorization = httpOptions != null ? httpOptions.authorization : null; + + final int[] allPorts = resolveAllPorts(openApi); + final int[] httpPorts = resolvePortsForScheme(openApi, "http"); + final int[] httpsPorts = resolvePortsForScheme(openApi, "https"); + final boolean secure = httpsPorts != null; + final Map securitySchemes = resolveSecuritySchemes(openApi); + final boolean hasJwt = !securitySchemes.isEmpty(); + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/http", binding.qname)) + .inject(n -> this.injectCatalog(n, openApi)) + .binding() + .name("tcp_server0") + .type("tcp") + .kind(SERVER) + .options(TcpOptionsConfig::builder) + .host("0.0.0.0") + .ports(allPorts) + .build() + .inject(b -> this.injectPlainTcpRoute(b, httpPorts, secure)) + .inject(b -> this.injectTlsTcpRoute(b, httpsPorts, secure)) + .build() + .inject(n -> this.injectTlsServer(n, tlsOption, secure)) + .binding() + .name("http_server0") + .type("http") + .kind(SERVER) + .options(HttpOptionsConfig::builder) + .access() + .policy(CROSS_ORIGIN) + .build() + .inject(o -> this.injectHttpServerOptions(o, authorization, hasJwt)) + .inject(r -> this.injectHttpServerRequests(r, openApi)) + .build() + .inject(b -> this.injectHttpServerRoutes(b, openApi, binding.qname, guardName, securitySchemes)) + .build() + .build() + .build(); + } + + private BindingConfigBuilder injectPlainTcpRoute( + BindingConfigBuilder binding, + int[] httpPorts, + boolean secure) + { + if (secure) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(httpPorts) + .build() + .exit("http_server0") + .build(); + } + return binding; + } + + private BindingConfigBuilder injectTlsTcpRoute( + BindingConfigBuilder binding, + int[] httpsPorts, + boolean secure) + { + if (secure) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(httpsPorts) + .build() + .exit("tls_server0") + .build(); + } + return binding; + } + + private NamespaceConfigBuilder injectTlsServer( + NamespaceConfigBuilder namespace, + TlsOptionsConfig tls, + boolean secure) + { + if (secure) + { + namespace + .binding() + .name("tls_server0") + .type("tls") + .kind(SERVER) + .options(tls) + .vault("server") + .exit("http_server0") + .build(); + } + return namespace; + } + + private HttpOptionsConfigBuilder injectHttpServerOptions( + HttpOptionsConfigBuilder options, + HttpAuthorizationConfig authorization, + boolean hasJwt) + { + if (hasJwt) + { + options.authorization(authorization).build(); + } + return options; + } + + private HttpOptionsConfigBuilder injectHttpServerRequests( + HttpOptionsConfigBuilder options, + OpenApi openApi) + { + for (String pathName : openApi.paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + OpenApiOperation operation = path.methods().get(methodName); + if (operation.requestBody != null || operation.parameters != null && !operation.parameters.isEmpty()) + { + options + .request() + .path(pathName) + .method(HttpRequestConfig.Method.valueOf(methodName)) + .inject(request -> injectContent(request, operation, openApi)) + .inject(request -> injectParams(request, operation)) + .build(); + } + } + } + return options; + } + + private HttpRequestConfigBuilder injectContent( + HttpRequestConfigBuilder request, + OpenApiOperation operation, + OpenApi openApi) + { + if (operation.requestBody != null && operation.requestBody.content != null && !operation.requestBody.content.isEmpty()) + { + OpenApiSchemaView schema = resolveSchemaForJsonContentType(operation.requestBody.content, openApi); + if (schema != null) + { + request. + content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .schema() + .subject(schema.refKey()) + .build() + .build() + .build(); + } + } + return request; + } + + private HttpRequestConfigBuilder injectParams( + HttpRequestConfigBuilder request, + OpenApiOperation operation) + { + if (operation != null && operation.parameters != null) + { + for (OpenApiParameter parameter : operation.parameters) + { + if (parameter.schema != null && parameter.schema.type != null) + { + ModelConfig model = models.get(parameter.schema.type); + if (model != null) + { + switch (parameter.in) + { + case "path": + request. + pathParam() + .name(parameter.name) + .model(model) + .build(); + break; + case "query": + request. + queryParam() + .name(parameter.name) + .model(model) + .build(); + break; + case "header": + request. + header() + .name(parameter.name) + .model(model) + .build(); + break; + } + } + } + } + } + return request; + } + + private BindingConfigBuilder injectHttpServerRoutes( + BindingConfigBuilder binding, + OpenApi openApi, + String qname, + String guardName, + Map securitySchemes) + { + for (String item : openApi.paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(item)); + for (String method : path.methods().keySet()) + { + binding + .route() + .exit(qname) + .when(HttpConditionConfig::builder) + .header(":path", item.replaceAll("\\{[^}]+\\}", "*")) + .header(":method", method) + .build() + .inject(route -> injectHttpServerRouteGuarded(route, path, method, guardName, securitySchemes)) + .build(); + } + } + return binding; + } + + private RouteConfigBuilder injectHttpServerRouteGuarded( + RouteConfigBuilder route, + OpenApiPathView path, + String method, + String guardName, + Map securitySchemes) + { + final List>> security = path.methods().get(method).security; + final boolean hasJwt = securitySchemes.isEmpty(); + + if (security != null) + { + for (Map> securityItem : security) + { + for (String securityItemLabel : securityItem.keySet()) + { + if (hasJwt && "jwt".equals(securitySchemes.get(securityItemLabel))) + { + route + .guarded() + .name(guardName) + .inject(guarded -> injectGuardedRoles(guarded, securityItem.get(securityItemLabel))) + .build(); + } + } + } + } + return route; + } + + private GuardedConfigBuilder injectGuardedRoles( + GuardedConfigBuilder guarded, + List roles) + { + for (String role : roles) + { + guarded.role(role); + } + return guarded; + } + + private NamespaceConfigBuilder injectCatalog( + NamespaceConfigBuilder namespace, + OpenApi openApi) + { + if (openApi.components != null && + openApi.components.schemas != null && + !openApi.components.schemas.isEmpty()) + { + namespace + .catalog() + .name(INLINE_CATALOG_NAME) + .type(INLINE_CATALOG_TYPE) + .options(InlineOptionsConfig::builder) + .subjects() + .inject(s -> this.injectSubjects(s, openApi)) + .build() + .build() + .build(); + } + return namespace; + } + + private InlineSchemaConfigBuilder injectSubjects( + InlineSchemaConfigBuilder subjects, + OpenApi openApi) + { + try (Jsonb jsonb = JsonbBuilder.create()) + { + for (Map.Entry entry : openApi.components.schemas.entrySet()) + { + subjects + .subject(entry.getKey()) + .version(VERSION_LATEST) + .schema(jsonb.toJson(openApi.components.schemas)) + .build(); + } + } + catch (Exception ex) + { + rethrowUnchecked(ex); + } + return subjects; + } + + private int[] resolveAllPorts( + OpenApi openApi) + { + int[] ports = new int[openApi.servers.size()]; + for (int i = 0; i < openApi.servers.size(); i++) + { + OpenApiServerView server = OpenApiServerView.of(openApi.servers.get(i)); + URI url = server.url(); + ports[i] = url.getPort(); + } + return ports; + } + + private int[] resolvePortsForScheme( + OpenApi openApi, + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(openApi, scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + private URI findFirstServerUrlWithScheme( + OpenApi openApi, + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (OpenApiServer item : openApi.servers) + { + OpenApiServerView server = OpenApiServerView.of(item); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } + + private Map resolveSecuritySchemes( + OpenApi openApi) + { + requireNonNull(openApi); + Map result = new Object2ObjectHashMap<>(); + if (openApi.components != null && + openApi.components.securitySchemes != null) + { + for (String securitySchemeName : openApi.components.securitySchemes.keySet()) + { + String guardType = openApi.components.securitySchemes.get(securitySchemeName).bearerFormat; + if ("jwt".equals(guardType)) + { + result.put(securitySchemeName, guardType); + } + } + } + return result; + } + + private OpenApiSchemaView resolveSchemaForJsonContentType( + Map content, + OpenApi openApi) + { + OpenApiMediaType mediaType = null; + if (content != null) + { + for (String contentType : content.keySet()) + { + if (jsonContentType.reset(contentType).matches()) + { + mediaType = content.get(contentType); + break; + } + } + } + + return mediaType == null ? null : OpenApiSchemaView.of(openApi.components.schemas, mediaType.schema); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java new file mode 100644 index 0000000000..d6c6404a31 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +public class OpenApi +{ + public String openapi; + public List servers; + public Map paths; + public OpenApiComponents components; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java new file mode 100644 index 0000000000..db34f20fff --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiBearerAuth +{ + public String bearerFormat; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java new file mode 100644 index 0000000000..1f31bb5592 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class OpenApiComponents +{ + public Map securitySchemes; + public Map schemas; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java new file mode 100644 index 0000000000..f91603be21 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiHeader +{ + public OpenApiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java new file mode 100644 index 0000000000..b7ff06d8e3 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiItem +{ + public String type; + public String description; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java new file mode 100644 index 0000000000..67635b7d26 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiMediaType +{ + public OpenApiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java new file mode 100644 index 0000000000..fe9aca2858 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +public class OpenApiOperation +{ + public List>> security; + public String operationId; + public OpenApiRequestBody requestBody; + public List parameters; + public Map responses; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java new file mode 100644 index 0000000000..fb713f91d3 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiParameter +{ + public String name; + public String in; + public boolean required; + public OpenApiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java new file mode 100644 index 0000000000..245899b8ee --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java @@ -0,0 +1,22 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class OpenApiRequestBody +{ + public Map content; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java new file mode 100644 index 0000000000..3f23331a88 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiResponse +{ + public OpenApiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java new file mode 100644 index 0000000000..855472a883 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class OpenApiSchema +{ + public String type; + public OpenApiSchema items; + public Map properties; + public List required; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java new file mode 100644 index 0000000000..4d27f750ea --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiSecurityScheme +{ + public String bearerFormat; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java new file mode 100644 index 0000000000..da292428f3 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenApiServer +{ + public String url; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java new file mode 100644 index 0000000000..64a976faa4 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class PathItem +{ + public OpenApiOperation get; + public OpenApiOperation put; + public OpenApiOperation post; + public OpenApiOperation delete; + public OpenApiOperation options; + public OpenApiOperation head; + public OpenApiOperation patch; + public OpenApiOperation trace; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java new file mode 100644 index 0000000000..280e28eaee --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class ResponseByContentType +{ + public Map headers; + public Map content; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java new file mode 100644 index 0000000000..658a7a5e84 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java @@ -0,0 +1,1066 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiBindingConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.OpenapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class OpenapiClientFactory implements OpenapiStreamFactory +{ + private static final String HTTP_TYPE_NAME = "http"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final OpenapiBeginExFW openapiBeginExRO = new OpenapiBeginExFW(); + + private final OpenapiBeginExFW.Builder openBeginExRW = new OpenapiBeginExFW.Builder(); + + private final OpenapiConfiguration config; + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int openapiTypeId; + private final int httpTypeId; + + + public OpenapiClientFactory( + OpenapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME); + this.httpTypeId = context.supplyTypeId(HTTP_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return httpTypeId; + } + + @Override + public int routedTypeId() + { + return openapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + OpenapiBindingConfig openapiBinding = new OpenapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, openapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + final OpenapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + final long apiId = openapiBeginEx.apiId(); + final String operationId = openapiBeginEx.operationId().asString(); + + final long resolvedId = binding.resolveResolvedId(apiId); + + if (resolvedId != -1) + { + newStream = new OpenapiStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + resolvedId, + operationId)::onOpenapiMessage; + } + + } + + return newStream; + } + + private final class OpenapiStream + { + private final HttpStream http; + private final MessageConsumer sender; + private final String operationId; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long replyBudgetId; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private OpenapiStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + String operationId) + { + this.http = new HttpStream(this, routedId, resolvedId, authorization); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onOpenapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onOpenapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onOpenapiData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onOpenapiEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onOpenapiFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onOpenapiAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onOpenapiWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onOpenapiReset(reset); + break; + default: + break; + } + } + + private void onOpenapiBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpBegin(traceId, openapiBeginEx.extension()); + } + + private void onOpenapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + http.doHttpData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onOpenapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpEnd(traceId, extension); + } + + private void onOpenapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + http.doHttpFlush(traceId, reserved, extension); + } + + private void onOpenapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpAbort(traceId, extension); + } + + private void onOpenapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = OpenapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onOpenapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + http.doHttpWindow(traceId, acknowledge, budgetId, padding); + } + + private void doOpenapiBegin( + long traceId, + OctetsFW extension) + { + state = OpenapiState.openingReply(state); + + final OpenapiBeginExFW openBeginEx = openBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(openapiTypeId) + .operationId(operationId) + .extension(extension) + .build(); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, openBeginEx); + } + + private void doOpenapiData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doOpenapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, reserved, extension); + + replySeq += reserved; + } + + private void doOpenapiEnd( + long traceId, + OctetsFW extension) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = OpenapiState.closeReply(state); + } + + private void doOpenapiAbort( + long traceId) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = OpenapiState.closeInitial(state); + } + + private void doOpenapiReset( + long traceId) + { + if (!OpenapiState.initialClosed(state)) + { + state = OpenapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doOpenapiWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = http.initialAck; + initialMax = http.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doOpenapiReset(traceId); + doOpenapiAbort(traceId); + + http.cleanup(traceId); + } + } + + private final class HttpStream + { + private final OpenapiStream delegate; + private final long originId; + private final long routedId; + private final long authorization; + + private final long initialId; + private final long replyId; + + private MessageConsumer receiver; + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + + private HttpStream( + OpenapiStream delegate, + long originId, + long routedId, + long authorization) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.authorization = authorization; + } + + private void onHttpMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onHttpBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onHttpData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onHttpFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onHttpEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onHttpAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onHttpReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onHttpWindow(window); + break; + default: + break; + } + } + + private void onHttpBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiState.openingReply(state); + + delegate.doOpenapiBegin(traceId, extension); + } + + private void onHttpData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiData(traceId, flags, reserved, payload, extension); + } + + private void onHttpFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiFlush(traceId, reserved, extension); + } + + private void onHttpEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiEnd(traceId, extension); + } + + private void onHttpAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiAbort(traceId); + } + + private void onHttpReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doOpenapiReset(traceId); + } + + private void onHttpWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + delegate.doOpenapiWindow(authorization, traceId, budgetId, padding); + } + + private void doHttpBegin( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialOpening(state)) + { + assert state == 0; + + this.receiver = newStream(this::onHttpMessage, originId, routedId, initialId, initialSeq, + initialAck, initialMax, traceId, authorization, 0L, extension); + state = OpenapiState.openingInitial(state); + } + } + + private void doHttpData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doHttpFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doHttpEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doHttpAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doHttpReset( + long traceId) + { + if (!OpenapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiState.closeReply(state); + } + } + + private void doHttpWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - padding, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doHttpAbort(traceId, EMPTY_OCTETS); + doHttpReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java new file mode 100644 index 0000000000..95b9ae0233 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java @@ -0,0 +1,1070 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiBindingConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiRouteConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.OpenapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiServerFactory implements OpenapiStreamFactory +{ + private static final String HTTP_TYPE_NAME = "http"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final OpenapiBeginExFW openapiBeginExRO = new OpenapiBeginExFW(); + private final HttpBeginExFW httpBeginExRO = new HttpBeginExFW(); + + private final OpenapiBeginExFW.Builder openapiBeginExRW = new OpenapiBeginExFW.Builder(); + + private final OpenapiConfiguration config; + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int openapiTypeId; + private final int httpTypeId; + + public OpenapiServerFactory( + OpenapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME); + this.httpTypeId = context.supplyTypeId(HTTP_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return httpTypeId; + } + + @Override + public int routedTypeId() + { + return openapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + OpenapiBindingConfig openapiBinding = new OpenapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, openapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final HttpBeginExFW httpBeginEx = extension.get(httpBeginExRO::tryWrap); + + final OpenapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null && binding.isCompositeNamespace(NamespacedId.namespaceId(originId))) + { + + final OpenapiRouteConfig route = binding.resolve(authorization); + + if (route != null) + { + final String operationId = binding.resolveOperationId(httpBeginEx); + + newStream = new HttpStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + route.id, + operationId)::onHttpMessage; + } + + } + + return newStream; + } + + private final class HttpStream + { + private final OpenapiStream openapi; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long replyBudgetId; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private HttpStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + String operationId) + { + this.openapi = new OpenapiStream(this, routedId, resolvedId, authorization, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onHttpMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onHttpBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onHttpData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onHttpEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onHttpFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onHttpAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onHttpWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onHttpReset(reset); + break; + default: + break; + } + } + + private void onHttpBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final long authorization = begin.authorization(); + final long affinity = begin.affinity(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiBegin(traceId, extension); + } + + private void onHttpData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + openapi.doOpenapiData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onHttpEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiEnd(traceId, extension); + } + + private void onHttpFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + openapi.doOpenapiFlush(traceId, reserved, extension); + } + + private void onHttpAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiAbort(traceId, extension); + } + + private void onHttpReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = OpenapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onHttpWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyPad = padding; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + openapi.doOpenapiWindow(traceId, acknowledge, budgetId, padding); + } + + private void doHttpBegin( + long traceId, + OctetsFW extension) + { + state = OpenapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doHttpData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doHttpFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, reserved, extension); + + replySeq += reserved; + } + + private void doHttpEnd( + long traceId, + OctetsFW extension) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = OpenapiState.closeReply(state); + } + + private void doHttpAbort( + long traceId) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = OpenapiState.closeInitial(state); + } + + private void doHttpReset( + long traceId) + { + if (!OpenapiState.initialClosed(state)) + { + state = OpenapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doHttpWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = openapi.initialAck; + initialMax = openapi.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doHttpReset(traceId); + doHttpAbort(traceId); + + openapi.cleanup(traceId); + } + } + + private final class OpenapiStream + { + private final HttpStream delegate; + private final String operationId; + private final long originId; + private final long routedId; + private final long authorization; + + private final long initialId; + private final long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private OpenapiStream( + HttpStream delegate, + long originId, + long routedId, + long authorization, + String operationId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.operationId = operationId; + } + + private void onOpenapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onOpenapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onOpenapiData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onOpenapiFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onOpenapiEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onOpenapiAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onOpenapiReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onOpenapiWindow(window); + break; + default: + break; + } + } + + private void onOpenapiBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiState.openingReply(state); + + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + delegate.doHttpBegin(traceId, openapiBeginEx.extension()); + } + + private void onOpenapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doHttpData(traceId, flags, reserved, payload, extension); + } + + private void onOpenapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doHttpFlush(traceId, reserved, extension); + } + + private void onOpenapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doHttpEnd(traceId, extension); + } + + private void onOpenapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doHttpAbort(traceId); + } + + private void onOpenapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doHttpReset(traceId); + } + + + private void onOpenapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doHttpWindow(authorization, traceId, budgetId, padding); + } + + private void doOpenapiBegin( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialOpening(state)) + { + assert state == 0; + + final OpenapiBeginExFW openapiBeginEx = openapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(openapiTypeId) + .operationId(operationId) + .extension(extension) + .build(); + + this.receiver = newStream(this::onOpenapiMessage, originId, routedId, initialId, initialSeq, + initialAck, initialMax, traceId, authorization, 0L, openapiBeginEx); + state = OpenapiState.openingInitial(state); + } + } + + private void doOpenapiData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doOpenapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doOpenapiEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doOpenapiAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doOpenapiReset( + long traceId) + { + if (!OpenapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiState.closeReply(state); + } + } + + private void doOpenapiWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void cleanup( + long traceId) + { + doOpenapiAbort(traceId, EMPTY_OCTETS); + doOpenapiReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java new file mode 100644 index 0000000000..687aae313f --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +public final class OpenapiState +{ + private static final int INITIAL_OPENING = 0x10; + private static final int INITIAL_OPENED = 0x20; + private static final int INITIAL_CLOSING = 0x40; + private static final int INITIAL_CLOSED = 0x80; + private static final int REPLY_OPENING = 0x01; + private static final int REPLY_OPENED = 0x02; + private static final int REPLY_CLOSING = 0x04; + private static final int REPLY_CLOSED = 0x08; + + static int openingInitial( + int state) + { + return state | INITIAL_OPENING; + } + + static int openInitial( + int state) + { + return openingInitial(state) | INITIAL_OPENED; + } + + static int closingInitial( + int state) + { + return state | INITIAL_CLOSING; + } + + static int closeInitial( + int state) + { + return closingInitial(state) | INITIAL_CLOSED; + } + + static boolean initialOpening( + int state) + { + return (state & INITIAL_OPENING) != 0; + } + + static boolean initialOpened( + int state) + { + return (state & INITIAL_OPENED) != 0; + } + + static boolean initialClosing( + int state) + { + return (state & INITIAL_CLOSING) != 0; + } + + static boolean initialClosed( + int state) + { + return (state & INITIAL_CLOSED) != 0; + } + + static boolean closed( + int state) + { + return initialClosed(state) && replyClosed(state); + } + + static int openingReply( + int state) + { + return state | REPLY_OPENING; + } + + static int openReply( + int state) + { + return state | REPLY_OPENED; + } + + static boolean replyOpening( + int state) + { + return (state & REPLY_OPENING) != 0; + } + + static boolean replyOpened( + int state) + { + return (state & REPLY_OPENED) != 0; + } + + static int closingReply( + int state) + { + return state | REPLY_CLOSING; + } + + static boolean replyClosing( + int state) + { + return (state & REPLY_CLOSING) != 0; + } + + static int closeReply( + int state) + { + return closingReply(state) | REPLY_CLOSED; + } + + static boolean replyClosed( + int state) + { + return (state & REPLY_CLOSED) != 0; + } + + private OpenapiState() + { + // utility + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java new file mode 100644 index 0000000000..706267b5ef --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public interface OpenapiStreamFactory extends BindingHandler +{ + void attach( + BindingConfig binding); + + void detach( + long bindingId); +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java new file mode 100644 index 0000000000..4a5baa320b --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.ResponseByContentType; + +public final class OpenApiOperationView +{ + public static final String DEFAULT = "default"; + + private final OpenApiOperation operation; + private final boolean hasResponses; + + private OpenApiOperationView( + OpenApiOperation operation) + { + this.operation = operation; + this.hasResponses = initHasResponses(); + } + + public Map responsesByStatus() + { + return operation.responses; + } + + public boolean hasResponses() + { + return hasResponses; + } + + private boolean initHasResponses() + { + boolean result = false; + if (operation != null && operation.responses != null) + { + for (Map.Entry response0 : operation.responses.entrySet()) + { + String status = response0.getKey(); + ResponseByContentType response1 = response0.getValue(); + if (!(DEFAULT.equals(status)) && response1.content != null) + { + result = true; + break; + } + } + } + return result; + } + + public static OpenApiOperationView of( + OpenApiOperation operation) + { + return new OpenApiOperationView(operation); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java new file mode 100644 index 0000000000..63265476d5 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; + +public final class OpenApiOperationsView +{ + private final Map> operationsByPath; + private final boolean hasResponses; + + public boolean hasResponses() + { + return this.hasResponses; + } + + public OpenApiOperationView operation( + String pathName, + String methodName) + { + return operationsByPath.get(pathName).get(methodName); + } + + public static OpenApiOperationsView of( + Map paths) + { + return new OpenApiOperationsView(paths); + } + + private OpenApiOperationsView( + Map paths) + { + this.operationsByPath = new Object2ObjectHashMap<>(); + boolean hasResponses = false; + for (String pathName : paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + OpenApiOperationView operation = OpenApiOperationView.of(path.methods().get(methodName)); + hasResponses |= operation.hasResponses(); + if (operationsByPath.containsKey(pathName)) + { + operationsByPath.get(pathName).put(methodName, operation); + } + else + { + Map operationsPerMethod = new LinkedHashMap<>(); + operationsPerMethod.put(methodName, operation); + operationsByPath.put(pathName, operationsPerMethod); + } + } + } + this.hasResponses = hasResponses; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java new file mode 100644 index 0000000000..4e80c12958 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import static java.util.Collections.unmodifiableMap; + +import java.util.LinkedHashMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; + +public final class OpenApiPathView +{ + private final Map methods; + + public Map methods() + { + return methods; + } + + public static OpenApiPathView of( + PathItem pathItem) + { + return new OpenApiPathView(pathItem); + } + + private OpenApiPathView( + PathItem pathItem) + { + Map methods = new LinkedHashMap<>(); + putIfNotNull(methods, "GET", pathItem.get); + putIfNotNull(methods, "PUT", pathItem.put); + putIfNotNull(methods, "POST", pathItem.post); + putIfNotNull(methods, "DELETE", pathItem.delete); + putIfNotNull(methods, "OPTIONS", pathItem.options); + putIfNotNull(methods, "HEAD", pathItem.head); + putIfNotNull(methods, "PATCH", pathItem.patch); + putIfNotNull(methods, "TRACE", pathItem.trace); + this.methods = unmodifiableMap(methods); + } + + private static void putIfNotNull( + Map methods, + String method, + OpenApiOperation operation) + { + if (operation != null) + { + methods.put(method, operation); + } + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java new file mode 100644 index 0000000000..fc5babcde0 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class OpenApiResolvable +{ + private final Map map; + private final Matcher matcher; + + protected String key; + + public OpenApiResolvable( + Map map, + String regex) + { + this.map = map; + this.matcher = Pattern.compile(regex).matcher(""); + } + + protected T resolveRef( + String ref) + { + T result = null; + if (matcher.reset(ref).matches()) + { + key = matcher.group(1); + result = map.get(key); + } + return result; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java new file mode 100644 index 0000000000..29eddefb26 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbPropertyOrder; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiSchema; + +@JsonbPropertyOrder({ + "type", + "items", + "properties", + "required" +}) +public final class OpenApiSchemaView extends OpenApiResolvable +{ + private static final String ARRAY_TYPE = "array"; + + private final OpenApiSchema schema; + private final Map schemas; + + private OpenApiSchemaView( + Map schemas, + OpenApiSchema schema) + { + super(schemas, "#/components/schemas/(\\w+)"); + if (schema.ref != null) + { + schema = resolveRef(schema.ref); + } + else if (ARRAY_TYPE.equals(schema.type) && schema.items != null && schema.items.ref != null) + { + schema.items = resolveRef(schema.items.ref); + } + this.schemas = schemas; + this.schema = schema; + } + + public String refKey() + { + return key; + } + + public String getType() + { + return schema.type; + } + + public OpenApiSchemaView getItems() + { + return schema.items == null ? null : OpenApiSchemaView.of(schemas, schema.items); + } + + public Map getProperties() + { + return schema.properties; + } + + public List getRequired() + { + return schema.required; + } + + public static OpenApiSchemaView of( + Map schemas, + OpenApiSchema schema) + { + return new OpenApiSchemaView(schemas, schema); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java new file mode 100644 index 0000000000..2a78362d66 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.net.URI; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; + +public final class OpenApiServerView +{ + private URI url; + + private OpenApiServerView( + OpenApiServer server) + { + this.url = URI.create(server.url); + } + + public URI url() + { + return url; + } + + public static OpenApiServerView of( + OpenApiServer server) + { + return new OpenApiServerView(server); + } +} diff --git a/incubator/binding-openapi/src/main/moditect/module-info.java b/incubator/binding-openapi/src/main/moditect/module-info.java new file mode 100644 index 0000000000..3a3f23238d --- /dev/null +++ b/incubator/binding-openapi/src/main/moditect/module-info.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.binding.openapi +{ + requires io.aklivity.zilla.runtime.engine; + requires io.aklivity.zilla.runtime.binding.http; + requires io.aklivity.zilla.runtime.binding.tcp; + requires io.aklivity.zilla.runtime.binding.tls; + requires io.aklivity.zilla.runtime.catalog.inline; + requires io.aklivity.zilla.runtime.guard.jwt; + requires io.aklivity.zilla.runtime.vault.filesystem; + requires io.aklivity.zilla.runtime.model.core; + requires io.aklivity.zilla.runtime.model.json; + + exports io.aklivity.zilla.runtime.binding.openapi.config; + + opens io.aklivity.zilla.runtime.binding.openapi.internal.model; + + provides io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi + with io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBindingFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiCompositeBindingAdapter; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiOptionsConfigAdapter; +} diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi new file mode 100644 index 0000000000..fa663f0820 --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBindingFactorySpi diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi new file mode 100644 index 0000000000..499d92471c --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiCompositeBindingAdapter diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..52d34a108e --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiOptionsConfigAdapter diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java new file mode 100644 index 0000000000..1ea1d03090 --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import static io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration.OPENAPI_TARGET_ROUTE_ID; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class OpenapiConfigurationTest +{ + public static final String OPENAPI_TARGET_ROUTE_ID_NAME = "zilla.binding.openapi.target.route.id"; + + @Test + public void shouldVerifyConstants() throws Exception + { + assertEquals(OPENAPI_TARGET_ROUTE_ID.name(), OPENAPI_TARGET_ROUTE_ID_NAME); + } +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..5acbad38d6 --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static java.util.function.Function.identity; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.specs.binding.openapi.OpenapiSpecs; + +public class OpenapiOptionsConfigAdapterTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + + private Jsonb jsonb; + + @Before + public void initJson() throws IOException + { + try (InputStream resource = OpenapiSpecs.class.getResourceAsStream("config/openapi/petstore.yaml")) + { + String content = new String(resource.readAllBytes(), UTF_8); + Mockito.doReturn(content).when(context).readURL("openapi/petstore.yaml"); + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("openapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + } + + @Test + public void shouldReadOptions() + { + String text = + "{" + + " \"tls\": {" + + " \"keys\": [" + + " \"localhost\"" + + " ]," + + " \"alpn\": [" + + " \"localhost\"" + + " ]" + + " }," + + " \"http\": {" + + " \"authorization\": {" + + " \"test0\": {" + + " \"credentials\": {" + + " \"cookies\": {" + + " \"access_token\": \"{credentials}\"" + + " }," + + " \"headers\": {" + + " \"authorization\": \"Bearer {credentials}\"" + + " }," + + " \"query\": {" + + " \"access_token\": \"{credentials}\"" + + " }" + + " }" + + " }" + + " }" + + " }," + + " \"specs\": [" + + " \"openapi/petstore.yaml\"" + + " ]" + + " }"; + + OpenapiOptionsConfig options = jsonb.fromJson(text, OpenapiOptionsConfig.class); + OpenapiConfig openapi = options.openapis.stream().findFirst().get(); + PathItem path = openapi.openapi.paths.get("/pets"); + + assertThat(options, not(nullValue())); + assertThat(path.post, not(nullValue())); + assertThat(options.tls, not(nullValue())); + assertThat(options.http, not(nullValue())); + } + + @Test + public void shouldWriteOptions() + { + String expected = "{\"tcp\":{\"host\":\"localhost\",\"port\":8080},\"tls\":{\"sni\":[\"example.net\"]}," + + "\"specs\":[\"openapi/petstore.yaml\"]}"; + + TcpOptionsConfig tcp = TcpOptionsConfig.builder() + .inject(identity()) + .host("localhost") + .ports(new int[] { 8080 }) + .build(); + + TlsOptionsConfig tls = TlsOptionsConfig.builder() + .inject(identity()) + .sni(asList("example.net")) + .build(); + + OpenapiOptionsConfig options = new OpenapiOptionsConfig(tcp, tls, null, asList( + new OpenapiConfig("openapi/petstore.yaml", new OpenApi()))); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertEquals(expected, text); + } +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java new file mode 100644 index 0000000000..db61283ebf --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import static io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfigurationTest.OPENAPI_TARGET_ROUTE_ID_NAME; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.ScriptProperty; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class OpenapiClientIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http") + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config") + .external("http0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.yaml") + @Specification({ + "${openapi}/create.pet/client", + "${http}/create.pet/server" + }) + @ScriptProperty("serverAddress \"zilla://streams/http0\"") + @Configure(name = OPENAPI_TARGET_ROUTE_ID_NAME, value = "4294967298") + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java new file mode 100644 index 0000000000..d50830204d --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class OpenapiServerIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http") + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config") + .external("openapi0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${http}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server-secure.yaml") + @Specification({ + "${http}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreateSecurePet() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${http}/reject.non.composite.origin/client" + }) + public void shouldRejectNonCompositeOrigin() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/pom.xml b/incubator/pom.xml index 846204631d..5a01348fa2 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -19,6 +19,7 @@ binding-amqp.spec binding-asyncapi.spec + binding-openapi.spec catalog-inline.spec catalog-schema-registry.spec exporter-otlp.spec @@ -29,6 +30,7 @@ binding-amqp binding-asyncapi + binding-openapi catalog-inline catalog-schema-registry @@ -58,6 +60,11 @@ binding-asyncapi ${project.version} + + ${project.groupId} + binding-openapi + ${project.version} + ${project.groupId} catalog-inline diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java index 7bb3baefd3..83b62ff766 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java @@ -17,6 +17,8 @@ import static java.util.function.Function.identity; +import java.util.function.Function; + public final class HttpAuthorizationConfig { public final String name; @@ -27,6 +29,12 @@ public static HttpAuthorizationConfigBuilder builder() return new HttpAuthorizationConfigBuilder<>(identity()); } + public static HttpAuthorizationConfigBuilder builder( + Function mapper) + { + return new HttpAuthorizationConfigBuilder(mapper); + } + HttpAuthorizationConfig( String name, HttpCredentialsConfig credentials) diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java index 5f16beb53b..6a7bcff7d3 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java @@ -26,19 +26,6 @@ public final class HttpAuthorizationConfigBuilder extends ConfigBuilder mapper) - { - this.mapper = mapper; - } - - @Override - @SuppressWarnings("unchecked") - protected Class> thisType() - { - return (Class>) getClass(); - } - public HttpAuthorizationConfigBuilder name( String name) { @@ -57,6 +44,20 @@ public T build() return mapper.apply(new HttpAuthorizationConfig(name, credentials)); } + + HttpAuthorizationConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + private HttpAuthorizationConfigBuilder credentials( HttpCredentialsConfig credentials) { diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java index 0271061d3c..bce304ea2f 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java @@ -101,7 +101,7 @@ public HttpRequestConfigBuilder> request() return new HttpRequestConfigBuilder<>(this::request); } - private HttpOptionsConfigBuilder authorization( + public HttpOptionsConfigBuilder authorization( HttpAuthorizationConfig authorization) { this.authorization = authorization; diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json index 2ae7449415..8c45102023 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json @@ -147,88 +147,7 @@ } ] }, - "authorization": - { - "title": "Authorizations", - "type": "object", - "patternProperties": - { - "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": - { - "title": "Authorization", - "type": "object", - "properties": - { - "credentials": - { - "title": "Credentials", - "type": "object", - "properties": - { - "cookies": - { - "title": "Cookies", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - }, - "headers": - { - "title": "Headers", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - }, - "query": - { - "title": "Query Parameters", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - } - }, - "additionalProperties": false, - "anyOf": - [ - { - "required": - [ - "cookies" - ] - }, - { - "required": - [ - "headers" - ] - }, - { - "required": - [ - "query" - ] - } - ] - } - }, - "additionalProperties": false, - "required": - [ - "credentials" - ] - } - }, - "maxProperties": 1 - }, + "authorization": "$defs/options/binding/http/authorization", "overrides": { "title": "Overrides", @@ -469,5 +388,94 @@ ] } } + }, + { + "op": "add", + "path": "/$defs/options/binding/http", + "value": + { + "authorization": + { + "title": "Authorizations", + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "title": "Authorization", + "type": "object", + "properties": + { + "credentials": + { + "title": "Credentials", + "type": "object", + "properties": + { + "cookies": + { + "title": "Cookies", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + }, + "headers": + { + "title": "Headers", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + }, + "query": + { + "title": "Query Parameters", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + } + }, + "additionalProperties": false, + "anyOf": + [ + { + "required": + [ + "cookies" + ] + }, + { + "required": + [ + "headers" + ] + }, + { + "required": + [ + "query" + ] + } + ] + } + }, + "additionalProperties": false, + "required": + [ + "credentials" + ] + } + }, + "maxProperties": 1 + } + } } ] diff --git a/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json b/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json index b1ef9202ab..dd2cc277ad 100644 --- a/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json +++ b/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json @@ -303,6 +303,12 @@ } ] }, + "options": + { + "binding": + { + } + }, "binding": { "title": "Binding", From a152baaed46d1febe1722068bcf5249d035f8409 Mon Sep 17 00:00:00 2001 From: bmaidics Date: Mon, 26 Feb 2024 19:09:47 +0100 Subject: [PATCH 10/25] Support asyncapi http proxy using asyncapi.yaml (#799) --- incubator/binding-asyncapi.spec/NOTICE | 2 + incubator/binding-asyncapi.spec/pom.xml | 7 +- .../asyncapi/config/client.http.secure.yaml | 41 +++ .../binding/asyncapi/config/client.http.yaml | 29 ++ .../asyncapi/config/client.mqtt.secure.yaml | 41 +++ .../binding/asyncapi/config/client.mqtt.yaml | 29 ++ .../asyncapi/config/http/asyncapi.yaml | 70 +++++ .../asyncapi/config/server.http.secure.yaml | 37 +++ .../binding/asyncapi/config/server.http.yaml | 26 ++ .../asyncapi/config/server.mqtt.secure.yaml | 37 +++ .../binding/asyncapi/config/server.mqtt.yaml | 26 ++ .../schema/asyncapi.schema.patch.json | 12 +- .../asyncapi/http/create.pet/client.rpt | 50 +++ .../asyncapi/http/create.pet/server.rpt | 53 ++++ .../mqtt/publish.and.subscribe/client.rpt | 92 ++++++ .../mqtt/publish.and.subscribe/server.rpt | 91 ++++++ .../streams/http/create.pet/client.rpt | 43 +++ .../streams/http/create.pet/server.rpt | 47 +++ .../binding/asyncapi/config/SchemaTest.java | 48 ++- .../asyncapi/streams/asyncapi/AsyncapiIT.java | 14 +- .../binding/asyncapi/streams/http/HttpIT.java | 49 +++ incubator/binding-asyncapi/pom.xml | 8 +- .../asyncapi/config/AsyncapiConfig.java | 6 +- .../config/AsyncapiOptionsConfig.java | 6 +- .../config/AsyncapiOptionsConfigBuilder.java | 11 +- ...AsyncapiClientCompositeBindingAdapter.java | 19 +- .../AsyncapiCompositeBindingAdapter.java | 64 ++-- .../internal/AsyncapiHttpProtocol.java | 295 ++++++++++++++++++ .../asyncapi/internal/AsyncapiProtocol.java | 95 ++++++ ...AsyncapiServerCompositeBindingAdapter.java | 139 ++------- .../internal/AyncapiMqttProtocol.java | 95 ++++++ .../config/AsyncapiBindingConfig.java | 168 +++++++++- .../config/AsyncapiOptionsConfigAdapter.java | 18 ++ .../internal/model/AsyncapiOperation.java | 2 +- .../internal/model/AsyncapiParameter.java | 2 +- .../internal/model/AsyncapiServer.java | 1 + .../stream/AsyncapiServerFactory.java | 16 +- .../internal/view/AsyncapiServerView.java | 5 + .../asyncapi/internal/view/AsyncapiView.java | 81 +++++ .../src/main/moditect/module-info.java | 6 + .../AsyncapiOptionsConfigAdapterTest.java | 2 +- .../internal/stream/client/AsyncapiIT.java | 19 +- .../internal/stream/server/AsyncapiIT.java | 16 +- 43 files changed, 1716 insertions(+), 202 deletions(-) create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/http/HttpIT.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiView.java diff --git a/incubator/binding-asyncapi.spec/NOTICE b/incubator/binding-asyncapi.spec/NOTICE index b7e575e904..6c158382cf 100644 --- a/incubator/binding-asyncapi.spec/NOTICE +++ b/incubator/binding-asyncapi.spec/NOTICE @@ -16,7 +16,9 @@ This project includes: ICU4J under Unicode/ICU License Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 zilla::specs::binding-mqtt.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 zilla::specs::engine.spec under The Apache Software License, Version 2.0 diff --git a/incubator/binding-asyncapi.spec/pom.xml b/incubator/binding-asyncapi.spec/pom.xml index 9a561905e4..66d07acf09 100644 --- a/incubator/binding-asyncapi.spec/pom.xml +++ b/incubator/binding-asyncapi.spec/pom.xml @@ -46,6 +46,11 @@ binding-mqtt.spec ${project.version} + + ${project.groupId} + binding-http.spec + ${project.version} + junit junit @@ -83,7 +88,7 @@ flyweight-maven-plugin ${project.version} - core asyncapi + core http mqtt asyncapi io.aklivity.zilla.specs.binding.asyncapi.internal.types diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml new file mode 100644 index 0000000000..5c0e2e216e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml @@ -0,0 +1,41 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + asyncapi0: + type: asyncapi + kind: client + vault: test0 + options: + specs: + - http/asyncapi.yaml + tcp: + host: localhost + port: + - 7080 + tls: + trust: + - serverca + trustcacerts: true + sni: + - http.example.net + alpn: + - h2 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml new file mode 100644 index 0000000000..457b87c52c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml @@ -0,0 +1,29 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + asyncapi0: + type: asyncapi + kind: client + options: + specs: + - http/asyncapi.yaml + tcp: + host: localhost + port: + - 7080 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml new file mode 100644 index 0000000000..47ebc84ec3 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml @@ -0,0 +1,41 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + asyncapi0: + type: asyncapi + kind: client + vault: test0 + options: + specs: + - mqtt/asyncapi.yaml + tcp: + host: localhost + port: + - 7183 + tls: + trust: + - serverca + trustcacerts: true + sni: + - mqtt.example.net + alpn: + - mqtt diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml new file mode 100644 index 0000000000..00b2ea5605 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml @@ -0,0 +1,29 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + asyncapi0: + type: asyncapi + kind: client + options: + specs: + - mqtt/asyncapi.yaml + tcp: + host: localhost + port: + - 7183 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml new file mode 100644 index 0000000000..8e5a0d70ac --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml @@ -0,0 +1,70 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +asyncapi: 3.0.0 +info: + title: AsyncAPI Petstore + license: + name: MIT + version: 1.0.0 +servers: + plain: + host: http://localhost:8080 + protocol: http + protocolVersion: '2.0' +defaultContentType: application/json + +channels: + pets: + address: /pets + showPetById: + address: /pets/{id} + +operations: + createPet: + action: send + bindings: + http: + type: request + method: POST + channel: + $ref: '#/channels/pets' + listPets: + action: receive + bindings: + http: + type: request + method: GET + channel: + $ref: '#/channels/pets' + getPets: + action: receive + bindings: + http: + type: request + method: GET + query: + type: object + properties: + limit: + type: number + channel: + $ref: '#/channels/showPetById' + +components: + correlationIds: + petsCorrelationId: + location: '$message.header#/idempotency-key' diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml new file mode 100644 index 0000000000..3be1dd33a6 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml @@ -0,0 +1,37 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + composite0: + type: asyncapi + kind: server + vault: test0 + options: + specs: + - http/asyncapi.yaml + tls: + keys: + - localhost + sni: + - http.example.net + alpn: + - h2 + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml new file mode 100644 index 0000000000..c3d11e19bb --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + composite0: + type: asyncapi + kind: server + options: + specs: + - http/asyncapi.yaml + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml new file mode 100644 index 0000000000..25e5d1bcd5 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml @@ -0,0 +1,37 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + composite0: + type: asyncapi + kind: server + vault: test0 + options: + specs: + - mqtt/asyncapi.yaml + tls: + keys: + - localhost + sni: + - mqtt.example.net + alpn: + - mqtt + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml new file mode 100644 index 0000000000..8e65b8c21c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + composite0: + type: asyncapi + kind: server + options: + specs: + - mqtt/asyncapi.yaml + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json index c99755c283..4cb1640a3a 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json @@ -45,7 +45,17 @@ } }, "tcp": "#/$defs/binding/tcp/options", - "tls": "#/$defs/binding/tls/options" + "tls": "#/$defs/binding/tls/options", + "http": + { + "title": "Http", + "type": "object", + "properties": + { + "authorization": "$defs/options/binding/http/authorization" + }, + "additionalProperties": false + } }, "additionalProperties": false }, diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt new file mode 100644 index 0000000000..763b53d28c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt @@ -0,0 +1,50 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .operationId("createPet") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "8") + .build()) + .build()} +connected + +write "{\"test\"}" +write close + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .operationId("createPet") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "21") + .build()) + .build()} + +read "{\"message\": \"string\"}" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt new file mode 100644 index 0000000000..fd958f606e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt @@ -0,0 +1,53 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .operationId("createPet") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "8") + .build()) + .build()} +connected + +read "{\"test\"}" +read closed + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .operationId("createPet") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "21") + .build()) + .build()} + +write "{\"message\": \"string\"}" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt new file mode 100644 index 0000000000..7e16896b71 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt @@ -0,0 +1,92 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +connected + +read zilla:data.empty + + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} +write "asyncapiMessage" +write flush + + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()) + .build()} +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt new file mode 100644 index 0000000000..6155cf6cb4 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt @@ -0,0 +1,91 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +connected + +write zilla:data.empty +write flush + + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} +read "asyncapiMessage" + + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()) + .build()} + +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/client.rpt new file mode 100644 index 0000000000..519bc10f7d --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/client.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/http" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "8") + .build()} +connected + +write "{\"test\"}" +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "21") + .build()} + +read "{\"message\": \"string\"}" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/server.rpt new file mode 100644 index 0000000000..3f504e0c61 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/server.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "8") + .build()} +connected + +read "{\"test\"}" +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "21") + .build()} + +write "{\"message\": \"string\"}" +write flush diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java index 8e0a08d69b..eb2f207fe5 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java @@ -35,33 +35,65 @@ public class SchemaTest .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config"); @Test - public void shouldValidateClient() + public void shouldValidateMqttClient() { - JsonObject config = schema.validate("client.yaml"); + JsonObject config = schema.validate("client.mqtt.yaml"); assertThat(config, not(nullValue())); } @Test - public void shouldValidateSecureClient() + public void shouldValidateMqttSecureClient() { - JsonObject config = schema.validate("client.secure.yaml"); + JsonObject config = schema.validate("client.mqtt.secure.yaml"); assertThat(config, not(nullValue())); } @Test - public void shouldValidateServer() + public void shouldValidateMqttServer() { - JsonObject config = schema.validate("server.yaml"); + JsonObject config = schema.validate("server.mqtt.yaml"); assertThat(config, not(nullValue())); } @Test - public void shouldValidateSecureServer() + public void shouldValidateMqttSecureServer() { - JsonObject config = schema.validate("server.secure.yaml"); + JsonObject config = schema.validate("server.mqtt.secure.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateHttpClient() + { + JsonObject config = schema.validate("client.http.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateHttpSecureClient() + { + JsonObject config = schema.validate("client.http.secure.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateHttpServer() + { + JsonObject config = schema.validate("server.http.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateHttpSecureServer() + { + JsonObject config = schema.validate("server.http.secure.yaml"); assertThat(config, not(nullValue())); } diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java index 332a42dd35..96d6a5be38 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java @@ -38,11 +38,21 @@ public class AsyncapiIT @Test @Specification({ - "${asyncapi}/publish.and.subscribe/client", - "${asyncapi}/publish.and.subscribe/server" + "${asyncapi}/mqtt/publish.and.subscribe/client", + "${asyncapi}/mqtt/publish.and.subscribe/server" }) public void shouldPublishAndSubscribe() throws Exception { k3po.finish(); } + + @Test + @Specification({ + "${asyncapi}/http/create.pet/client", + "${asyncapi}/http/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } } diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/http/HttpIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/http/HttpIT.java new file mode 100644 index 0000000000..ccddb26bbd --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/http/HttpIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams.http; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class HttpIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + + @Test + @Specification({ + "${http}/create.pet/client", + "${http}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi/pom.xml b/incubator/binding-asyncapi/pom.xml index 04ac004485..40bbb982ba 100644 --- a/incubator/binding-asyncapi/pom.xml +++ b/incubator/binding-asyncapi/pom.xml @@ -55,6 +55,12 @@ ${project.version} provided + + io.aklivity.zilla + binding-http + ${project.version} + provided + io.aklivity.zilla binding-tcp @@ -151,7 +157,7 @@ flyweight-maven-plugin ${project.version} - core mqtt asyncapi + core mqtt http asyncapi io.aklivity.zilla.runtime.binding.asyncapi.internal.types diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java index e23d8812ac..c289b113e9 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java @@ -19,13 +19,13 @@ public class AsyncapiConfig { public final String location; - public final Asyncapi asyncApi; + public final Asyncapi asyncapi; public AsyncapiConfig( String location, - Asyncapi asyncApi) + Asyncapi asyncapi) { this.location = location; - this.asyncApi = asyncApi; + this.asyncapi = asyncapi; } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java index 3723d7644b..daa394055e 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.function.Function; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; @@ -26,6 +27,7 @@ public final class AsyncapiOptionsConfig extends OptionsConfig public final List specs; public final TcpOptionsConfig tcp; public final TlsOptionsConfig tls; + public final HttpOptionsConfig http; public static AsyncapiOptionsConfigBuilder builder() { @@ -41,9 +43,11 @@ public static AsyncapiOptionsConfigBuilder builder( public AsyncapiOptionsConfig( List specs, TcpOptionsConfig tcp, - TlsOptionsConfig tls) + TlsOptionsConfig tls, + HttpOptionsConfig http) { this.specs = specs; + this.http = http; this.tcp = tcp; this.tls = tls; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java index 86455c6e56..1dfbddaceb 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.function.Function; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; @@ -29,6 +30,7 @@ public final class AsyncapiOptionsConfigBuilder extends ConfigBuilder specs; private TcpOptionsConfig tcp; private TlsOptionsConfig tls; + private HttpOptionsConfig http; AsyncapiOptionsConfigBuilder( Function mapper) @@ -64,10 +66,17 @@ public AsyncapiOptionsConfigBuilder tls( return this; } + public AsyncapiOptionsConfigBuilder http( + HttpOptionsConfig http) + { + this.http = http; + return this; + } + @Override public T build() { - return mapper.apply(new AsyncapiOptionsConfig(specs, tcp, tls)); + return mapper.apply(new AsyncapiOptionsConfig(specs, tcp, tls, http)); } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java index 3809ac126e..3892e398c7 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java @@ -18,6 +18,8 @@ import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiView; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; @@ -37,19 +39,22 @@ public BindingConfig adapt( { AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; AsyncapiConfig asyncapiConfig = options.specs.get(0); - this.asyncApi = asyncapiConfig.asyncApi; + this.asyncapi = asyncapiConfig.asyncapi; + AsyncapiView asyncapiView = AsyncapiView.of(asyncapi); - int[] mqttsPorts = resolvePortsForScheme("mqtts"); - this.isTlsEnabled = mqttsPorts != null; + //TODO: add composite for all servers + AsyncapiServerView firstServer = AsyncapiServerView.of(asyncapi.servers.entrySet().iterator().next().getValue()); this.qname = binding.qname; this.qvault = binding.qvault; - + this.protocol = resolveProtocol(firstServer.protocol(), options); + int[] compositeSecurePorts = asyncapiView.resolvePortsForScheme(protocol.secureScheme); + this.isTlsEnabled = compositeSecurePorts != null; return BindingConfig.builder(binding) .composite() - .name(String.format(qname, "$composite")) + .name(String.format("%s.%s", qname, "$composite")) .binding() - .name("mqtt_client0") - .type("mqtt") + .name(String.format("%s_client0", protocol.scheme)) + .type(protocol.scheme) .kind(CLIENT) .exit(isTlsEnabled ? "tls_client0" : "tcp_client0") .build() diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java index d4e9245b87..02a7ccf455 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java @@ -14,68 +14,46 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal; -import static java.util.Objects.requireNonNull; - -import java.net.URI; import java.util.regex.Matcher; import java.util.regex.Pattern; +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; public class AsyncapiCompositeBindingAdapter { - protected static final String INLINE_CATALOG_NAME = "catalog0"; protected static final String APPLICATION_JSON = "application/json"; - protected static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); - protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); - protected Asyncapi asyncApi; + protected Asyncapi asyncapi; protected boolean isTlsEnabled; + protected AsyncapiProtocol protocol; protected String qname; protected String qvault; - protected int[] resolveAllPorts() - { - int[] ports = new int[asyncApi.servers.size()]; - String[] keys = asyncApi.servers.keySet().toArray(String[]::new); - for (int i = 0; i < asyncApi.servers.size(); i++) - { - AsyncapiServerView server = AsyncapiServerView.of(asyncApi.servers.get(keys[i])); - URI url = server.url(); - ports[i] = url.getPort(); - } - return ports; - } - - protected int[] resolvePortsForScheme( - String scheme) - { - requireNonNull(scheme); - int[] ports = null; - URI url = findFirstServerUrlWithScheme(scheme); - if (url != null) - { - ports = new int[] {url.getPort()}; - } - return ports; - } - - protected URI findFirstServerUrlWithScheme( - String scheme) + protected AsyncapiProtocol resolveProtocol( + String protocolName, + AsyncapiOptionsConfig options) { - requireNonNull(scheme); - URI result = null; - for (String key : asyncApi.servers.keySet()) + Pattern pattern = Pattern.compile("(http|mqtt)"); + Matcher matcher = pattern.matcher(protocolName); + AsyncapiProtocol protocol = null; + if (matcher.find()) { - AsyncapiServerView server = AsyncapiServerView.of(asyncApi.servers.get(key)); - if (scheme.equals(server.url().getScheme())) + switch (matcher.group()) { - result = server.url(); + case "http": + protocol = new AsyncapiHttpProtocol(qname, asyncapi, options); + break; + case "mqtt": + protocol = new AyncapiMqttProtocol(qname, asyncapi, options); break; } } - return result; + else + { + // TODO: should we do something? + } + return protocol; } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java new file mode 100644 index 0000000000..f01ba1b949 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java @@ -0,0 +1,295 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.binding.http.config.HttpPolicyConfig.CROSS_ORIGIN; +import static java.util.Objects.requireNonNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiItem; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiParameter; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiServer; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; +import io.aklivity.zilla.runtime.binding.http.config.HttpAuthorizationConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpConditionConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig.Method; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.GuardedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; +import io.aklivity.zilla.runtime.model.core.config.IntegerModelConfig; +import io.aklivity.zilla.runtime.model.core.config.StringModelConfig; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public class AsyncapiHttpProtocol extends AsyncapiProtocol +{ + private static final Map MODELS = Map.of( + "string", StringModelConfig.builder().build(), + "integer", IntegerModelConfig.builder().build() + ); + private static final String SCHEME = "http"; + private static final String SECURE_SCHEME = "https"; + private final Map securitySchemes; + private final String authorizationHeader; + private final boolean isJwtEnabled; + private final String guardName; + private final HttpAuthorizationConfig authorization; + + protected AsyncapiHttpProtocol( + String qname, + Asyncapi asyncApi, + AsyncapiOptionsConfig options) + { + super(qname, asyncApi, SCHEME, SECURE_SCHEME); + this.securitySchemes = resolveSecuritySchemes(); + this.authorizationHeader = resolveAuthorizationHeader(); + this.isJwtEnabled = !securitySchemes.isEmpty(); + final HttpOptionsConfig httpOptions = options.http; + this.guardName = httpOptions != null ? httpOptions.authorization.name : null; + this.authorization = httpOptions != null ? httpOptions.authorization : null; + } + + + @Override + public BindingConfigBuilder injectProtocolServerOptions( + BindingConfigBuilder binding) + { + return binding + .options(HttpOptionsConfig::builder) + .access() + .policy(CROSS_ORIGIN) + .build() + .inject(this::injectHttpServerOptions) + .inject(this::injectHttpServerRequests) + .build(); + } + + @Override + public BindingConfigBuilder injectProtocolServerRoutes( + BindingConfigBuilder binding) + { + for (Map.Entry entry : asyncApi.servers.entrySet()) + { + AsyncapiServerView server = AsyncapiServerView.of(entry.getValue()); + for (String name : asyncApi.operations.keySet()) + { + AsyncapiOperation operation = asyncApi.operations.get(name); + AsyncapiChannelView channel = AsyncapiChannelView.of(asyncApi.channels, operation.channel); + String path = channel.address().replaceAll("\\{[^}]+\\}", "*"); + String method = operation.bindings.get("http").method; + binding + .route() + .exit(qname) + .when(HttpConditionConfig::builder) + .header(":scheme", server.scheme()) + .header(":authority", server.authority()) + .header(":path", path) + .header(":method", method) + .build() + .inject(route -> injectHttpServerRouteGuarded(route, server)) + .build(); + } + } + return binding; + } + + private HttpOptionsConfigBuilder injectHttpServerOptions( + HttpOptionsConfigBuilder options) + { + if (isJwtEnabled) + { + options.authorization(authorization).build(); + } + return options; + } + + private HttpOptionsConfigBuilder injectHttpServerRequests( + HttpOptionsConfigBuilder options) + { + for (String name : asyncApi.operations.keySet()) + { + AsyncapiOperation operation = asyncApi.operations.get(name); + AsyncapiChannelView channel = AsyncapiChannelView.of(asyncApi.channels, operation.channel); + String path = channel.address(); + Method method = Method.valueOf(operation.bindings.get("http").method); + if (channel.messages() != null && !channel.messages().isEmpty() || + channel.parameters() != null && !channel.parameters().isEmpty()) + { + options + .request() + .path(path) + .method(method) + .inject(request -> injectContent(request, channel.messages())) + .inject(request -> injectPathParams(request, channel.parameters())) + .build(); + } + } + return options; + } + + private HttpRequestConfigBuilder injectContent( + HttpRequestConfigBuilder request, + Map messages) + { + if (messages != null) + { + if (hasJsonContentType()) + { + request. + content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .inject(catalog -> injectSchemas(catalog, messages)) + .build() + .build(); + } + } + return request; + } + + private CatalogedConfigBuilder injectSchemas( + CatalogedConfigBuilder catalog, + Map messages) + { + for (String name : messages.keySet()) + { + AsyncapiMessageView message = AsyncapiMessageView.of(asyncApi.components.messages, messages.get(name)); + String subject = message.refKey() != null ? message.refKey() : name; + catalog + .schema() + .subject(subject) + .build() + .build(); + } + return catalog; + } + + private HttpRequestConfigBuilder injectPathParams( + HttpRequestConfigBuilder request, + Map parameters) + { + if (parameters != null) + { + for (String name : parameters.keySet()) + { + AsyncapiParameter parameter = parameters.get(name); + if (parameter.schema != null && parameter.schema.type != null) + { + ModelConfig model = MODELS.get(parameter.schema.type); + if (model != null) + { + request + .pathParam() + .name(name) + .model(model) + .build(); + } + } + } + } + return request; + } + + private RouteConfigBuilder injectHttpServerRouteGuarded( + RouteConfigBuilder route, + AsyncapiServerView server) + { + if (server.security() != null) + { + for (Map> securityItem : server.security()) + { + for (String securityItemLabel : securityItem.keySet()) + { + if (isJwtEnabled && "jwt".equals(securitySchemes.get(securityItemLabel))) + { + route + .guarded() + .name(guardName) + .inject(guarded -> injectGuardedRoles(guarded, securityItem.get(securityItemLabel))) + .build(); + break; + } + } + } + } + return route; + } + + private GuardedConfigBuilder injectGuardedRoles( + GuardedConfigBuilder guarded, + List roles) + { + for (String role : roles) + { + guarded.role(role); + } + return guarded; + } + + private Map resolveSecuritySchemes() + { + requireNonNull(asyncApi); + Map result = new HashMap<>(); + if (asyncApi.components != null && asyncApi.components.securitySchemes != null) + { + for (String securitySchemeName : asyncApi.components.securitySchemes.keySet()) + { + String guardType = asyncApi.components.securitySchemes.get(securitySchemeName).bearerFormat; + if ("jwt".equals(guardType)) + { + result.put(securitySchemeName, guardType); + } + } + } + return result; + } + + private String resolveAuthorizationHeader() + { + requireNonNull(asyncApi); + requireNonNull(asyncApi.components); + String result = null; + if (asyncApi.components.messages != null) + { + for (Map.Entry entry : asyncApi.components.messages.entrySet()) + { + AsyncapiMessage message = entry.getValue(); + if (message.headers != null && message.headers.properties != null) + { + AsyncapiItem authorization = message.headers.properties.get("authorization"); + if (authorization != null) + { + result = authorization.description; + break; + } + } + } + } + return result; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java new file mode 100644 index 0000000000..52d678e7dd --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; + +public abstract class AsyncapiProtocol +{ + protected static final String INLINE_CATALOG_NAME = "catalog0"; + protected static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + + protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); + + protected Asyncapi asyncApi; + protected String qname; + public final String scheme; + public final String secureScheme; + + protected AsyncapiProtocol( + String qname, + Asyncapi asyncApi, + String scheme, + String secureScheme) + { + this.qname = qname; + this.asyncApi = asyncApi; + this.scheme = scheme; + this.secureScheme = secureScheme; + } + + public abstract BindingConfigBuilder injectProtocolServerOptions( + BindingConfigBuilder binding); + + public abstract BindingConfigBuilder injectProtocolServerRoutes( + BindingConfigBuilder binding); + + protected CatalogedConfigBuilder injectJsonSchemas( + CatalogedConfigBuilder cataloged, + Map messages, + String contentType) + { + for (Map.Entry messageEntry : messages.entrySet()) + { + AsyncapiMessageView message = + AsyncapiMessageView.of(asyncApi.components.messages, messageEntry.getValue()); + String schema = messageEntry.getKey(); + if (message.contentType().equals(contentType)) + { + cataloged + .schema() + .subject(schema) + .build() + .build(); + } + else + { + throw new RuntimeException("Invalid content type"); + } + } + return cataloged; + } + + protected boolean hasJsonContentType() + { + String contentType = null; + if (asyncApi.components != null && asyncApi.components.messages != null && + !asyncApi.components.messages.isEmpty()) + { + AsyncapiMessage firstAsyncapiMessage = asyncApi.components.messages.entrySet().stream() + .findFirst().get().getValue(); + contentType = AsyncapiMessageView.of(asyncApi.components.messages, firstAsyncapiMessage).contentType(); + } + return contentType != null && jsonContentType.reset(contentType).matches(); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java index ed262d71e7..ad994b3eee 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java @@ -16,29 +16,22 @@ import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; -import java.util.Map; - import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; -import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiChannel; -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.mqtt.config.MqttConditionConfig; -import io.aklivity.zilla.runtime.binding.mqtt.config.MqttOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiView; import io.aklivity.zilla.runtime.binding.tcp.config.TcpConditionConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; -import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; -import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; public class AsyncapiServerCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi { - private int[] mqttPorts; - private int[] mqttsPorts; + private int[] compositePorts; + private int[] compositeSecurePorts; private boolean isPlainEnabled; @Override @@ -53,19 +46,24 @@ public BindingConfig adapt( { AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; AsyncapiConfig asyncapiConfig = options.specs.get(0); - this.asyncApi = asyncapiConfig.asyncApi; + this.asyncapi = asyncapiConfig.asyncapi; + AsyncapiView asyncapiView = AsyncapiView.of(asyncapi); + + //TODO: add composite for all servers + AsyncapiServerView firstServer = AsyncapiServerView.of(asyncapi.servers.entrySet().iterator().next().getValue()); - int[] allPorts = resolveAllPorts(); - this.mqttPorts = resolvePortsForScheme("mqtt"); - this.mqttsPorts = resolvePortsForScheme("mqtts"); - this.isPlainEnabled = mqttPorts != null; - this.isTlsEnabled = mqttsPorts != null; this.qname = binding.qname; this.qvault = binding.qvault; + this.protocol = resolveProtocol(firstServer.protocol(), options); + int[] allPorts = asyncapiView.resolveAllPorts(); + this.compositePorts = asyncapiView.resolvePortsForScheme(protocol.scheme); + this.compositeSecurePorts = asyncapiView.resolvePortsForScheme(protocol.secureScheme); + this.isPlainEnabled = compositePorts != null; + this.isTlsEnabled = compositeSecurePorts != null; return BindingConfig.builder(binding) .composite() - .name(String.format("%s/mqtt", qname)) + .name(String.format("%s/%s", qname, protocol.scheme)) .binding() .name("tcp_server0") .type("tcp") @@ -79,11 +77,11 @@ public BindingConfig adapt( .build() .inject(n -> injectTlsServer(n, options)) .binding() - .name("mqtt_server0") - .type("mqtt") + .name(String.format("%s_server0", protocol.scheme)) + .type(protocol.scheme) .kind(SERVER) - .inject(this::injectMqttServerOptions) - .inject(this::injectMqttServerRoutes) + .inject(protocol::injectProtocolServerOptions) + .inject(protocol::injectProtocolServerRoutes) .build() .build() .build(); @@ -97,9 +95,9 @@ private BindingConfigBuilder injectPlainTcpRoute( binding .route() .when(TcpConditionConfig::builder) - .ports(mqttPorts) + .ports(compositePorts) .build() - .exit("mqtt_server0") + .exit(String.format("%s_server0", protocol.scheme)) .build(); } return binding; @@ -113,7 +111,7 @@ private BindingConfigBuilder injectTlsTcpRoute( binding .route() .when(TcpConditionConfig::builder) - .ports(mqttsPorts) + .ports(compositeSecurePorts) .build() .exit("tls_server0") .build(); @@ -138,98 +136,9 @@ private NamespaceConfigBuilder injectTlsServer( .alpn(options.tls.alpn) .build() .vault(qvault) - .exit("mqtt_server0") + .exit(String.format("%s_server0", protocol.scheme)) .build(); } return namespace; } - - private BindingConfigBuilder injectMqttServerOptions( - BindingConfigBuilder binding) - { - for (Map.Entry channelEntry : asyncApi.channels.entrySet()) - { - String topic = channelEntry.getValue().address.replaceAll("\\{[^}]+\\}", "#"); - Map messages = channelEntry.getValue().messages; - if (hasJsonContentType()) - { - binding - .options(MqttOptionsConfig::builder) - .topic() - .name(topic) - .content(JsonModelConfig::builder) - .catalog() - .name(INLINE_CATALOG_NAME) - .inject(cataloged -> injectJsonSchemas(cataloged, messages, APPLICATION_JSON)) - .build() - .build() - .build() - .build() - .build(); - } - } - return binding; - } - - private CatalogedConfigBuilder injectJsonSchemas( - CatalogedConfigBuilder cataloged, - Map messages, - String contentType) - { - for (Map.Entry messageEntry : messages.entrySet()) - { - AsyncapiMessageView message = AsyncapiMessageView.of(asyncApi.components.messages, messageEntry.getValue()); - String schema = messageEntry.getKey(); - if (message.contentType().equals(contentType)) - { - cataloged - .schema() - .subject(schema) - .build() - .build(); - } - else - { - throw new RuntimeException("Invalid content type"); - } - } - return cataloged; - } - - private BindingConfigBuilder injectMqttServerRoutes( - BindingConfigBuilder binding) - { - for (Map.Entry entry : asyncApi.channels.entrySet()) - { - String topic = entry.getValue().address.replaceAll("\\{[^}]+\\}", "#"); - binding - .route() - .when(MqttConditionConfig::builder) - .publish() - .topic(topic) - .build() - .build() - .when(MqttConditionConfig::builder) - .subscribe() - .topic(topic) - .build() - .build() - .exit(qname) - .build(); - } - return binding; - } - - private boolean hasJsonContentType() - { - String contentType = null; - if (asyncApi.components != null && asyncApi.components.messages != null && - !asyncApi.components.messages.isEmpty()) - { - AsyncapiMessage firstAsyncapiMessage = asyncApi.components.messages.entrySet().stream() - .findFirst().get().getValue(); - contentType = AsyncapiMessageView.of(asyncApi.components.messages, firstAsyncapiMessage).contentType(); - } - return contentType != null && jsonContentType.reset(contentType).matches(); - } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java new file mode 100644 index 0000000000..c9f9dee1b7 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiCompositeBindingAdapter.APPLICATION_JSON; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiChannel; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.mqtt.config.MqttConditionConfig; +import io.aklivity.zilla.runtime.binding.mqtt.config.MqttOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public class AyncapiMqttProtocol extends AsyncapiProtocol +{ + private static final String SCHEME = "mqtt"; + private static final String SECURE_SCHEME = "mqtts"; + + public AyncapiMqttProtocol( + String qname, + Asyncapi asyncApi, + AsyncapiOptionsConfig options) + { + super(qname, asyncApi, SCHEME, SECURE_SCHEME); + } + + @Override + public BindingConfigBuilder injectProtocolServerOptions( + BindingConfigBuilder binding) + { + for (Map.Entry channelEntry : asyncApi.channels.entrySet()) + { + String topic = channelEntry.getValue().address.replaceAll("\\{[^}]+\\}", "#"); + Map messages = channelEntry.getValue().messages; + if (hasJsonContentType()) + { + binding + .options(MqttOptionsConfig::builder) + .topic() + .name(topic) + .content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .inject(cataloged -> injectJsonSchemas(cataloged, messages, APPLICATION_JSON)) + .build() + .build() + .build() + .build() + .build(); + } + } + return binding; + } + + @Override + public BindingConfigBuilder injectProtocolServerRoutes( + BindingConfigBuilder binding) + { + for (Map.Entry entry : asyncApi.channels.entrySet()) + { + String topic = entry.getValue().address.replaceAll("\\{[^}]+\\}", "#"); + binding + .route() + .when(MqttConditionConfig::builder) + .publish() + .topic(topic) + .build() + .build() + .when(MqttConditionConfig::builder) + .subscribe() + .topic(topic) + .build() + .build() + .exit(qname) + .build(); + } + return binding; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java index 09d31cfd2c..2dc25ae748 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java @@ -14,17 +14,30 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; +import static java.util.Collections.unmodifiableMap; import static java.util.stream.Collector.of; import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; -import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -import org.agrona.collections.IntHashSet; +import org.agrona.AsciiSequenceView; +import org.agrona.DirectBuffer; +import org.agrona.collections.Int2ObjectHashMap; import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Object2ObjectHashMap; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.String16FW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.HttpBeginExFW; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.KindConfig; import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; @@ -36,9 +49,12 @@ public final class AsyncapiBindingConfig public final KindConfig kind; public final AsyncapiOptionsConfig options; public final List routes; - private final IntHashSet composites; + private final Int2ObjectHashMap composites; private final long overrideRouteId; private final Long2LongHashMap resolvedIds; + private final HttpHeaderHelper helper; + private final Object2ObjectHashMap paths; + private final Map operationIds; public AsyncapiBindingConfig( BindingConfig binding, @@ -53,25 +69,50 @@ public AsyncapiBindingConfig( this.resolvedIds = binding.composites.stream() .map(c -> c.bindings) .flatMap(List::stream) - .filter(b -> b.type.equals("mqtt")) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http")) .collect(of( () -> new Long2LongHashMap(-1), (m, r) -> m.put(0L, r.id), //TODO: populate proper apiId (m, r) -> m, IDENTITY_FINISH )); - this.composites = binding.composites.stream() + this.composites = new Int2ObjectHashMap<>(); + binding.composites.stream() .map(c -> c.bindings) .flatMap(List::stream) - .filter(b -> b.type.equals("mqtt")) - .map(b -> NamespacedId.namespaceId(b.id)) - .collect(toCollection(IntHashSet::new)); + .filter(b -> b.type.equals("mqtt") || b.type.equals("http")) + .forEach(b -> this.composites.put(NamespacedId.namespaceId(b.id), b.type)); + + this.paths = new Object2ObjectHashMap<>(); + options.specs.forEach(c -> c.asyncapi.channels.forEach((k, v) -> + { + String regex = v.address.replaceAll("\\{[^/]+}", "[^/]+"); + regex = "^" + regex + "$"; + Pattern pattern = Pattern.compile(regex); + paths.put(pattern.matcher(""), k); + })); + + this.helper = new HttpHeaderHelper(); + + Map resolversByMethod = new TreeMap<>(CharSequence::compare); + options.specs.forEach(c -> c.asyncapi.operations.forEach((k, v) -> + { + String[] refParts = v.channel.ref.split("/"); + resolversByMethod.put(refParts[refParts.length - 1], k); + })); + this.operationIds = unmodifiableMap(resolversByMethod); } public boolean isCompositeOriginId( long originId) { - return composites.contains(NamespacedId.namespaceId(originId)); + return composites.containsKey(NamespacedId.namespaceId(originId)); + } + + public String getCompositeOriginType( + long originId) + { + return composites.get(NamespacedId.namespaceId(originId)); } public long resolveResolvedId( @@ -80,6 +121,28 @@ public long resolveResolvedId( return overrideRouteId != -1 ? overrideRouteId : resolvedIds.get(apiId); } + public String resolveOperationId( + HttpBeginExFW httpBeginEx) + { + helper.visit(httpBeginEx); + + String operationId = null; + + for (Map.Entry item : paths.entrySet()) + { + Matcher matcher = item.getKey(); + matcher.reset(helper.path); + if (matcher.find()) + { + String channelName = item.getValue(); + operationId = operationIds.get(channelName); + break; + } + } + + return operationId; + } + public AsyncapiRouteConfig resolve( long authorization) { @@ -88,4 +151,91 @@ public AsyncapiRouteConfig resolve( .findFirst() .orElse(null); } + + private static final class HttpHeaderHelper + { + private static final String8FW HEADER_NAME_METHOD = new String8FW(":method"); + private static final String8FW HEADER_NAME_PATH = new String8FW(":path"); + private static final String8FW HEADER_NAME_SCHEME = new String8FW(":scheme"); + private static final String8FW HEADER_NAME_AUTHORITY = new String8FW(":authority"); + + private final Map> visitors; + { + Map> visitors = new HashMap<>(); + visitors.put(HEADER_NAME_METHOD, this::visitMethod); + visitors.put(HEADER_NAME_PATH, this::visitPath); + visitors.put(HEADER_NAME_SCHEME, this::visitScheme); + visitors.put(HEADER_NAME_AUTHORITY, this::visitAuthority); + this.visitors = visitors; + } + private final AsciiSequenceView methodRO = new AsciiSequenceView(); + private final AsciiSequenceView pathRO = new AsciiSequenceView(); + private final String16FW schemeRO = new String16FW(); + private final String16FW authorityRO = new String16FW(); + + public CharSequence path; + public CharSequence method; + public String16FW scheme; + public String16FW authority; + + private void visit( + HttpBeginExFW beginEx) + { + method = null; + path = null; + scheme = null; + authority = null; + + if (beginEx != null) + { + beginEx.headers().forEach(this::dispatch); + } + } + + private boolean dispatch( + HttpHeaderFW header) + { + final String8FW name = header.name(); + final Consumer visitor = visitors.get(name); + if (visitor != null) + { + visitor.accept(header.value()); + } + + return method != null && + path != null && + scheme != null && + authority != null; + } + + private void visitMethod( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + method = methodRO.wrap(buffer, offset, length); + } + + private void visitPath( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + path = pathRO.wrap(buffer, offset, length); + } + + private void visitScheme( + String16FW value) + { + scheme = schemeRO.wrap(value.buffer(), value.offset(), value.limit()); + } + + private void visitAuthority( + String16FW value) + { + authority = authorityRO.wrap(value.buffer(), value.offset(), value.limit()); + } + } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java index 4bb1288060..1df06bf50e 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java @@ -46,6 +46,7 @@ import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfigBuilder; import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; @@ -59,9 +60,11 @@ public final class AsyncapiOptionsConfigAdapter implements OptionsConfigAdapterS private static final String SPECS_NAME = "specs"; private static final String TCP_NAME = "tcp"; private static final String TLS_NAME = "tls"; + private static final String HTTP_NAME = "http"; private OptionsConfigAdapter tcpOptions; private OptionsConfigAdapter tlsOptions; + private OptionsConfigAdapter httpOptions; private Function readURL; public Kind kind() @@ -102,6 +105,12 @@ public JsonObject adaptToJson( object.add(TLS_NAME, tlsOptions.adaptToJson(tls)); } + if (asyncapiOptions.http != null) + { + final HttpOptionsConfig http = asyncapiOptions.http; + object.add(HTTP_NAME, httpOptions.adaptToJson(http)); + } + return object.build(); } @@ -130,6 +139,13 @@ public OptionsConfig adaptFromJson( asyncapiOptions.tls(tlsOptions); } + if (object.containsKey(HTTP_NAME)) + { + final JsonObject http = object.getJsonObject(HTTP_NAME); + final HttpOptionsConfig httpOptions = (HttpOptionsConfig) this.httpOptions.adaptFromJson(http); + asyncapiOptions.http(httpOptions); + } + return asyncapiOptions.build(); } @@ -142,6 +158,8 @@ public void adaptContext( this.tcpOptions.adaptType("tcp"); this.tlsOptions = new OptionsConfigAdapter(Kind.BINDING, context); this.tlsOptions.adaptType("tls"); + this.httpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.httpOptions.adaptType("http"); } private List asListAsyncapis( diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java index d7f721fb9c..4f79c24260 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java @@ -19,5 +19,5 @@ public class AsyncapiOperation { public Map bindings; - public AsyncapiChannel asyncapiChannel; + public AsyncapiChannel channel; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java index f915b15bc0..af864bab5b 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java @@ -16,5 +16,5 @@ public class AsyncapiParameter { - public AsyncapiSchema asyncapiSchema; + public AsyncapiSchema schema; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java index d9a85e1935..01d390d4a6 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java @@ -20,5 +20,6 @@ public class AsyncapiServer { public String host; + public String protocol; public List>> security; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java index c5470ef7c7..cbe69b5e6a 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java @@ -14,6 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; +import java.util.function.Function; import java.util.function.LongSupplier; import java.util.function.LongUnaryOperator; @@ -34,6 +35,7 @@ import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.DataFW; import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.EndFW; import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.HttpBeginExFW; import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.MqttBeginExFW; import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.ResetFW; import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.WindowFW; @@ -46,8 +48,11 @@ public final class AsyncapiServerFactory implements AsyncapiStreamFactory { private static final String MQTT_TYPE_NAME = "mqtt"; + private static final String HTTP_TYPE_NAME = "http"; private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); private final BeginFW beginRO = new BeginFW(); + private final BeginFW compositeBeginRO = new BeginFW(); + private final HttpBeginExFW httpBeginRO = new HttpBeginExFW(); private final DataFW dataRO = new DataFW(); private final EndFW endRO = new EndFW(); private final FlushFW flushRO = new FlushFW(); @@ -65,6 +70,7 @@ public final class AsyncapiServerFactory implements AsyncapiStreamFactory private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); private final MqttBeginExFW mqttBeginExRO = new MqttBeginExFW(); + private final HttpBeginExFW httpBeginExRO = new HttpBeginExFW(); private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); @@ -75,9 +81,11 @@ public final class AsyncapiServerFactory implements AsyncapiStreamFactory private final LongUnaryOperator supplyInitialId; private final LongUnaryOperator supplyReplyId; private final LongSupplier supplyTraceId; + private final Function supplyTypeId; private final Long2ObjectHashMap bindings; private final int asyncapiTypeId; private final int mqttTypeId; + private final int httpTypeId; private final AsyncapiConfiguration config; public AsyncapiServerFactory( @@ -91,10 +99,12 @@ public AsyncapiServerFactory( this.streamFactory = context.streamFactory(); this.supplyInitialId = context::supplyInitialId; this.supplyReplyId = context::supplyReplyId; + this.supplyTypeId = context::supplyTypeId; this.supplyTraceId = context::supplyTraceId; this.bindings = new Long2ObjectHashMap<>(); this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); this.mqttTypeId = context.supplyTypeId(MQTT_TYPE_NAME); + this.httpTypeId = context.supplyTypeId(HTTP_TYPE_NAME); } @Override @@ -139,7 +149,6 @@ public MessageConsumer newStream( final long affinity = begin.affinity(); final long authorization = begin.authorization(); final OctetsFW extension = begin.extension(); - final MqttBeginExFW mqttBeginEx = extension.get(mqttBeginExRO::tryWrap); final AsyncapiBindingConfig binding = bindings.get(routedId); @@ -151,8 +160,10 @@ public MessageConsumer newStream( if (route != null) { - final String operationId = null; + final int compositeTypeId = supplyTypeId.apply(binding.getCompositeOriginType(originId)); + final String operationId = compositeTypeId == httpTypeId ? + binding.resolveOperationId(extension.get(httpBeginRO::tryWrap)) : null; newStream = new CompositeStream( receiver, originId, @@ -163,7 +174,6 @@ public MessageConsumer newStream( route.id, operationId)::onCompositeMessage; } - } return newStream; diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java index 47d91d02d9..7f7c95d78d 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java @@ -34,6 +34,11 @@ public List>> security() return server.security; } + public String protocol() + { + return server.protocol; + } + public String scheme() { return url().getScheme(); diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiView.java new file mode 100644 index 0000000000..c05cb95ed1 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiView.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import static java.util.Objects.requireNonNull; + +import java.net.URI; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; + +public final class AsyncapiView +{ + private final Asyncapi asyncapi; + + public int[] resolveAllPorts() + { + int[] ports = new int[asyncapi.servers.size()]; + String[] keys = asyncapi.servers.keySet().toArray(String[]::new); + for (int i = 0; i < asyncapi.servers.size(); i++) + { + AsyncapiServerView server = AsyncapiServerView.of(asyncapi.servers.get(keys[i])); + URI url = server.url(); + ports[i] = url.getPort(); + } + return ports; + } + + public int[] resolvePortsForScheme( + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + public URI findFirstServerUrlWithScheme( + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (String key : asyncapi.servers.keySet()) + { + AsyncapiServerView server = AsyncapiServerView.of(asyncapi.servers.get(key)); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } + + public static AsyncapiView of( + Asyncapi asyncapi) + { + return new AsyncapiView(asyncapi); + } + + private AsyncapiView( + Asyncapi asyncapi) + { + this.asyncapi = asyncapi; + } +} diff --git a/incubator/binding-asyncapi/src/main/moditect/module-info.java b/incubator/binding-asyncapi/src/main/moditect/module-info.java index 046666982b..db4f24ab6a 100644 --- a/incubator/binding-asyncapi/src/main/moditect/module-info.java +++ b/incubator/binding-asyncapi/src/main/moditect/module-info.java @@ -16,8 +16,14 @@ { requires io.aklivity.zilla.runtime.engine; requires io.aklivity.zilla.runtime.binding.mqtt; + requires io.aklivity.zilla.runtime.binding.http; requires io.aklivity.zilla.runtime.binding.tcp; requires io.aklivity.zilla.runtime.binding.tls; + requires io.aklivity.zilla.runtime.catalog.inline; + requires io.aklivity.zilla.runtime.guard.jwt; + requires io.aklivity.zilla.runtime.vault.filesystem; + requires io.aklivity.zilla.runtime.model.core; + requires io.aklivity.zilla.runtime.model.json; opens io.aklivity.zilla.runtime.binding.asyncapi.internal.model; diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java index 54b6bd4af9..f2bb6d64c0 100644 --- a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java @@ -116,7 +116,7 @@ public void shouldReadOptions() assertThat(options, not(nullValue())); AsyncapiConfig asyncapi = options.specs.get(0); assertThat(asyncapi.location, equalTo("mqtt/asyncapi.yaml")); - assertThat(asyncapi.asyncApi, instanceOf(Asyncapi.class)); + assertThat(asyncapi.asyncapi, instanceOf(Asyncapi.class)); assertThat(options.tcp.host, equalTo("localhost")); assertThat(options.tcp.ports, equalTo(new int[] { 7183 })); assertThat(options.tls.keys, equalTo(asList("localhost"))); diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java index 584f972547..e02c9243bf 100644 --- a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java @@ -36,6 +36,7 @@ public class AsyncapiIT { private final K3poRule k3po = new K3poRule() .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt") + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http") .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); @@ -46,15 +47,16 @@ public class AsyncapiIT .configure(ENGINE_DRAIN_ON_CLOSE, false) .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config") .external("mqtt0") + .external("http0") .clean(); @Rule public final TestRule chain = outerRule(engine).around(k3po).around(timeout); @Test - @Configuration("client.yaml") + @Configuration("client.mqtt.yaml") @Specification({ - "${asyncapi}/publish.and.subscribe/client", + "${asyncapi}/mqtt/publish.and.subscribe/client", "${mqtt}/publish.and.subscribe/server" }) @Configure(name = ASYNCAPI_TARGET_ROUTE_ID_NAME, value = "4294967298") @@ -63,4 +65,17 @@ public void shouldPublishAndSubscribe() throws Exception { k3po.finish(); } + + @Test + @Configuration("client.http.yaml") + @Specification({ + "${asyncapi}/http/create.pet/client", + "${http}/create.pet/server" + }) + @Configure(name = ASYNCAPI_TARGET_ROUTE_ID_NAME, value = "4294967299") + @ScriptProperty("serverAddress \"zilla://streams/http0\"") + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } } diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java index 6a0545109c..9fb0d52045 100644 --- a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java @@ -33,6 +33,7 @@ public class AsyncapiIT { private final K3poRule k3po = new K3poRule() .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt") + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http") .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); @@ -49,13 +50,24 @@ public class AsyncapiIT public final TestRule chain = outerRule(engine).around(k3po).around(timeout); @Test - @Configuration("server.yaml") + @Configuration("server.mqtt.yaml") @Specification({ "${mqtt}/publish.and.subscribe/client", - "${asyncapi}/publish.and.subscribe/server" + "${asyncapi}/mqtt/publish.and.subscribe/server" }) public void shouldPublishAndSubscribe() throws Exception { k3po.finish(); } + + @Test + @Configuration("server.http.yaml") + @Specification({ + "${http}/create.pet/client", + "${asyncapi}/http/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } } From 0e6c0d69f250395eabba614de0d3b6416cf55a42 Mon Sep 17 00:00:00 2001 From: bmaidics Date: Mon, 26 Feb 2024 20:30:33 +0100 Subject: [PATCH 11/25] Support parameters in KafkaTopicsConfig (#809) --- .../internal/config/KafkaBindingConfig.java | 75 ++-------- .../kafka/internal/config/KafkaTopicType.java | 76 ++++++++++ .../KafkaCacheClientProduceFactory.java | 13 +- .../stream/KafkaCacheServerFetchFactory.java | 13 +- .../kafka/internal/stream/CacheMergedIT.java | 20 +++ ....options.parameterized.topic.validate.yaml | 65 ++++++++ .../client.rpt | 32 ++++ .../server.rpt | 46 ++++++ .../client.rpt | 39 +++++ .../server.rpt | 46 ++++++ .../client.rpt | 136 +++++++++++++++++ .../server.rpt | 139 ++++++++++++++++++ .../client.rpt | 136 +++++++++++++++++ .../server.rpt | 139 ++++++++++++++++++ .../kafka/streams/application/MergedIT.java | 36 +++++ 15 files changed, 938 insertions(+), 73 deletions(-) create mode 100644 runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicType.java create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/cache.options.parameterized.topic.validate.yaml create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/client.rpt create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/server.rpt create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/client.rpt create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/server.rpt create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/client.rpt create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/server.rpt create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/client.rpt create mode 100644 specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/server.rpt diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaBindingConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaBindingConfig.java index d590fead82..d88b2f81bb 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaBindingConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaBindingConfig.java @@ -15,13 +15,13 @@ */ package io.aklivity.zilla.runtime.binding.kafka.internal.config; +import static io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaTopicType.DEFAULT_TOPIC_TYPE; import static io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaOffsetType.HISTORICAL; import static java.util.stream.Collectors.toList; +import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.function.ToLongFunction; -import java.util.stream.Collectors; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; @@ -32,7 +32,6 @@ import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.KindConfig; -import io.aklivity.zilla.runtime.engine.model.ConverterHandler; public final class KafkaBindingConfig { @@ -42,10 +41,7 @@ public final class KafkaBindingConfig public final KindConfig kind; public final List routes; public final ToLongFunction resolveId; - public final Map keyReaders; - public final Map keyWriters; - public final Map valueReaders; - public final Map valueWriters; + public final List topicTypes; public KafkaBindingConfig( BindingConfig binding, @@ -57,38 +53,8 @@ public KafkaBindingConfig( this.options = KafkaOptionsConfig.class.cast(binding.options); this.routes = binding.routes.stream().map(KafkaRouteConfig::new).collect(toList()); this.resolveId = binding.resolveId; - this.keyReaders = options != null && options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap( - t -> t.name, - t -> t.key != null - ? context.supplyReadConverter(t.key) - : ConverterHandler.NONE)) - : null; - this.keyWriters = options != null && options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap( - t -> t.name, - t -> t.key != null - ? context.supplyWriteConverter(t.key) - : ConverterHandler.NONE)) - : null; - this.valueReaders = options != null && options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap( - t -> t.name, - t -> t.value != null - ? context.supplyReadConverter(t.value) - : ConverterHandler.NONE)) - : null; - this.valueWriters = options != null && options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap( - t -> t.name, - t -> t.value != null - ? context.supplyWriteConverter(t.value) - : ConverterHandler.NONE)) - : null; + this.topicTypes = options != null && options.topics != null + ? options.topics.stream().map(t -> new KafkaTopicType(context, t)).collect(toList()) : Collections.emptyList(); } public KafkaRouteConfig resolve( @@ -147,27 +113,18 @@ public KafkaOffsetType supplyDefaultOffset( return config != null && config.defaultOffset != null ? config.defaultOffset : HISTORICAL; } - public ConverterHandler resolveKeyReader( + public KafkaTopicType resolveTopicType( String topic) { - return keyReaders != null ? keyReaders.getOrDefault(topic, ConverterHandler.NONE) : ConverterHandler.NONE; - } - - public ConverterHandler resolveKeyWriter( - String topic) - { - return keyWriters != null ? keyWriters.getOrDefault(topic, ConverterHandler.NONE) : ConverterHandler.NONE; - } - - public ConverterHandler resolveValueReader( - String topic) - { - return valueReaders != null ? valueReaders.getOrDefault(topic, ConverterHandler.NONE) : ConverterHandler.NONE; - } - - public ConverterHandler resolveValueWriter( - String topic) - { - return valueWriters != null ? valueWriters.getOrDefault(topic, ConverterHandler.NONE) : ConverterHandler.NONE; + KafkaTopicType matchedType = DEFAULT_TOPIC_TYPE; + for (KafkaTopicType topicType : topicTypes) + { + if (topicType.matches(topic)) + { + matchedType = topicType; + break; + } + } + return matchedType; } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicType.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicType.java new file mode 100644 index 0000000000..cf217c1f58 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicType.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.internal.config; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfig; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.model.ConverterHandler; + +public class KafkaTopicType +{ + public static final KafkaTopicType DEFAULT_TOPIC_TYPE = new KafkaTopicType(); + + public final ConverterHandler keyReader; + public final ConverterHandler keyWriter; + public final ConverterHandler valueReader; + public final ConverterHandler valueWriter; + + private final Matcher topicMatch; + + private KafkaTopicType() + { + this.topicMatch = null; + this.keyReader = ConverterHandler.NONE; + this.keyWriter = ConverterHandler.NONE; + this.valueReader = ConverterHandler.NONE; + this.valueWriter = ConverterHandler.NONE; + } + + public KafkaTopicType( + EngineContext context, + KafkaTopicConfig topicConfig) + { + this.topicMatch = topicConfig.name != null ? asMatcher(topicConfig.name) : null; + this.keyReader = Optional.ofNullable(topicConfig.key) + .map(context::supplyReadConverter) + .orElse(ConverterHandler.NONE); + this.keyWriter = Optional.ofNullable(topicConfig.key) + .map(context::supplyWriteConverter) + .orElse(ConverterHandler.NONE); + this.valueReader = Optional.ofNullable(topicConfig.value) + .map(context::supplyReadConverter) + .orElse(ConverterHandler.NONE); + this.valueWriter = Optional.ofNullable(topicConfig.value) + .map(context::supplyWriteConverter) + .orElse(ConverterHandler.NONE); + } + + public boolean matches( + String topic) + { + return this.topicMatch == null || this.topicMatch.reset(topic).matches(); + } + + private static Matcher asMatcher( + String topic) + { + return Pattern.compile(topic.replaceAll("\\{[^}]+\\}", ".+")).matcher(""); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java index 51edb9867b..9f71397961 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java @@ -48,6 +48,7 @@ import io.aklivity.zilla.runtime.binding.kafka.internal.cache.KafkaCacheTopic; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaBindingConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaRouteConfig; +import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaTopicType; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Flyweight; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaAckMode; @@ -260,11 +261,10 @@ public MessageConsumer newStream( final KafkaCache cache = supplyCache.apply(cacheName); final KafkaCacheTopic topic = cache.supplyTopic(topicName); final KafkaCachePartition partition = topic.supplyProducePartition(partitionId, localIndex); - final ConverterHandler convertKey = binding.resolveKeyWriter(topicName); - final ConverterHandler convertValue = binding.resolveValueWriter(topicName); + final KafkaTopicType topicType = binding.resolveTopicType(topicName); final KafkaCacheClientProduceFan newFan = new KafkaCacheClientProduceFan(routedId, resolvedId, authorization, budget, - partition, cacheRoute, topicName, convertKey, convertValue); + partition, cacheRoute, topicName, topicType); cacheRoute.clientProduceFansByTopicPartition.put(partitionKey, newFan); fan = newFan; @@ -537,8 +537,7 @@ private KafkaCacheClientProduceFan( KafkaCachePartition partition, KafkaCacheRoute cacheRoute, String topicName, - ConverterHandler convertKey, - ConverterHandler convertValue) + KafkaTopicType topicType) { this.originId = originId; this.routedId = routedId; @@ -548,8 +547,8 @@ private KafkaCacheClientProduceFan( this.budget = budget; this.cacheRoute = cacheRoute; this.topicName = topicName; - this.convertKey = convertKey; - this.convertValue = convertValue; + this.convertKey = topicType.keyWriter; + this.convertValue = topicType.valueWriter; this.members = new Long2ObjectHashMap<>(); this.defaultOffset = KafkaOffsetType.LIVE; this.cursor = cursorFactory.newCursor( diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerFetchFactory.java index 4d72d26725..b6b340e1ff 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerFetchFactory.java @@ -52,6 +52,7 @@ import io.aklivity.zilla.runtime.binding.kafka.internal.cache.KafkaCacheTopic; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaBindingConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaRouteConfig; +import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaTopicType; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.ArrayFW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Flyweight; @@ -237,11 +238,10 @@ public MessageConsumer newStream( final KafkaCache cache = supplyCache.apply(cacheName); final KafkaCacheTopic cacheTopic = cache.supplyTopic(topicName); final KafkaCachePartition partition = cacheTopic.supplyFetchPartition(partitionId); - final ConverterHandler convertKey = binding.resolveKeyReader(topicName); - final ConverterHandler convertValue = binding.resolveValueReader(topicName); + final KafkaTopicType topicType = binding.resolveTopicType(topicName); final KafkaCacheServerFetchFanout newFanout = new KafkaCacheServerFetchFanout(routedId, resolvedId, authorization, - affinity, partition, routeDeltaType, defaultOffset, convertKey, convertValue); + affinity, partition, routeDeltaType, defaultOffset, topicType); cacheRoute.serverFetchFanoutsByTopicPartition.put(partitionKey, newFanout); fanout = newFanout; @@ -516,8 +516,7 @@ private KafkaCacheServerFetchFanout( KafkaCachePartition partition, KafkaDeltaType deltaType, KafkaOffsetType defaultOffset, - ConverterHandler convertKey, - ConverterHandler convertValue) + KafkaTopicType topicType) { this.originId = originId; this.routedId = routedId; @@ -528,8 +527,8 @@ private KafkaCacheServerFetchFanout( this.retentionMillisMax = defaultOffset == LIVE ? SECONDS.toMillis(30) : Long.MAX_VALUE; this.members = new ArrayList<>(); this.leaderId = leaderId; - this.convertKey = convertKey; - this.convertValue = convertValue; + this.convertKey = topicType.keyReader; + this.convertValue = topicType.valueReader; this.entryMark = new MutableInteger(0); this.valueMark = new MutableInteger(0); } diff --git a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/CacheMergedIT.java b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/CacheMergedIT.java index 3507a80750..dba2b931c1 100644 --- a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/CacheMergedIT.java +++ b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/CacheMergedIT.java @@ -257,6 +257,26 @@ public void shouldFetchMergedMessageValueValid() throws Exception k3po.finish(); } + @Test + @Configuration("cache.options.parameterized.topic.validate.yaml") + @Specification({ + "${app}/merged.fetch.from.parameterized.topic.value.valid/client", + "${app}/unmerged.fetch.from.parameterized.topic.value.valid/server"}) + public void shouldFetchMergedFromParameterizedTopicValid() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("cache.options.parameterized.topic.validate.yaml") + @Specification({ + "${app}/merged.fetch.from.parameterized.topic.value.invalid/client", + "${app}/unmerged.fetch.from.parameterized.topic.value.invalid/server"}) + public void shouldFetchMergedFromParameterizedTopicInvalid() throws Exception + { + k3po.finish(); + } + @Test @Configuration("cache.options.validate.yaml") @Specification({ diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/cache.options.parameterized.topic.validate.yaml b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/cache.options.parameterized.topic.validate.yaml new file mode 100644 index 0000000000..c0d01b7cb4 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/cache.options.parameterized.topic.validate.yaml @@ -0,0 +1,65 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +catalogs: + test0: + type: test + options: + id: 1 + schema: | + { + "fields": [ + { + "name": "id", + "type": "string" + }, + { + "name": "status", + "type": "string" + } + ], + "name": "Event", + "namespace": "io.aklivity.example", + "type": "record" + } +bindings: + app0: + type: kafka + kind: cache_client + routes: + - exit: cache0 + when: + - topic: test.* + cache0: + type: kafka + kind: cache_server + options: + topics: + - name: test.{id} + value: + model: test + capability: read + length: 13 + catalog: + test0: + - id: 1 + routes: + - exit: app1 + when: + - topic: test.* + diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/client.rpt new file mode 100644 index 0000000000..721f8e34cc --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/client.rpt @@ -0,0 +1,32 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app0" + option zilla:window 16 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("FETCH_ONLY") + .topic("test.id0") + .partition(0, 1) + .build() + .build()} + +connected + + diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/server.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/server.rpt new file mode 100644 index 0000000000..939c5ae746 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/server.rpt @@ -0,0 +1,46 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("FETCH_ONLY") + .topic("test.id0") + .partition(0, 1) + .build() + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .fetch() + .timestamp(newTimestamp) + .partition(0, 1, 2) + .build() + .build()} +write ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" +write flush diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/client.rpt new file mode 100644 index 0000000000..27f271c048 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/client.rpt @@ -0,0 +1,39 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app0" + option zilla:window 16 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("FETCH_ONLY") + .topic("test.id0") + .partition(0, 1) + .build() + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .fetch() + .partition(0, 1, 2) + .build() + .build()} +read ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/server.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/server.rpt new file mode 100644 index 0000000000..939c5ae746 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/server.rpt @@ -0,0 +1,46 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("FETCH_ONLY") + .topic("test.id0") + .partition(0, 1) + .build() + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .fetch() + .timestamp(newTimestamp) + .partition(0, 1, 2) + .build() + .build()} +write ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" +write flush diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/client.rpt new file mode 100644 index 0000000000..64830fe84f --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/client.rpt @@ -0,0 +1,136 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +read zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .describe() + .config("cleanup.policy", "delete") + .config("max.message.bytes", 1000012) + .config("segment.bytes", 1073741824) + .config("segment.index.bytes", 10485760) + .config("segment.ms", 604800000) + .config("retention.bytes", -1) + .config("retention.ms", 604800000) + .config("delete.retention.ms", 86400000) + .config("min.compaction.lag.ms", 0) + .config("max.compaction.lag.ms", 9223372036854775807) + .config("min.cleanable.dirty.ratio", 0.5) + .build() + .build()} + +read notify RECEIVED_CONFIG + +connect await RECEIVED_CONFIG + "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +read zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .meta() + .partition(0, 1) + .build() + .build()} +read notify PARTITION_COUNT_2 + +connect await PARTITION_COUNT_2 + "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, -2) + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, 1, 2) + .build() + .build()} + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .fetch() + .partition(0, 1, 2) + .build() + .build()} +read ${kafka:varint(3)} "id0" diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/server.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/server.rpt new file mode 100644 index 0000000000..4ed2c70790 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/server.rpt @@ -0,0 +1,139 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +accept "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .describe() + .config("cleanup.policy", "delete") + .config("max.message.bytes", 1000012) + .config("segment.bytes", 1073741824) + .config("segment.index.bytes", 10485760) + .config("segment.ms", 604800000) + .config("retention.bytes", -1) + .config("retention.ms", 604800000) + .config("delete.retention.ms", 86400000) + .config("min.compaction.lag.ms", 0) + .config("max.compaction.lag.ms", 9223372036854775807) + .config("min.cleanable.dirty.ratio", 0.5) + .build() + .build()} +write flush + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .meta() + .partition(0, 1) + .build() + .build()} +write flush + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, -2) + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, 1, 2) + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .fetch() + .timestamp(newTimestamp) + .partition(0, 1, 2) + .build() + .build()} +write ${kafka:varint(3)} "id0" +write flush diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/client.rpt new file mode 100644 index 0000000000..cfde43db7d --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/client.rpt @@ -0,0 +1,136 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +read zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .describe() + .config("cleanup.policy", "delete") + .config("max.message.bytes", 1000012) + .config("segment.bytes", 1073741824) + .config("segment.index.bytes", 10485760) + .config("segment.ms", 604800000) + .config("retention.bytes", -1) + .config("retention.ms", 604800000) + .config("delete.retention.ms", 86400000) + .config("min.compaction.lag.ms", 0) + .config("max.compaction.lag.ms", 9223372036854775807) + .config("min.cleanable.dirty.ratio", 0.5) + .build() + .build()} + +read notify RECEIVED_CONFIG + +connect await RECEIVED_CONFIG + "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +read zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .meta() + .partition(0, 1) + .build() + .build()} +read notify PARTITION_COUNT_2 + +connect await PARTITION_COUNT_2 + "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, -2) + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, 1, 2) + .build() + .build()} + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .fetch() + .partition(0, 1, 2) + .build() + .build()} +read ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/server.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/server.rpt new file mode 100644 index 0000000000..f05f35fced --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/server.rpt @@ -0,0 +1,139 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +accept "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .describe() + .config("cleanup.policy", "delete") + .config("max.message.bytes", 1000012) + .config("segment.bytes", 1073741824) + .config("segment.index.bytes", 10485760) + .config("segment.ms", 604800000) + .config("retention.bytes", -1) + .config("retention.ms", 604800000) + .config("delete.retention.ms", 86400000) + .config("min.compaction.lag.ms", 0) + .config("max.compaction.lag.ms", 9223372036854775807) + .config("min.cleanable.dirty.ratio", 0.5) + .build() + .build()} +write flush + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .meta() + .partition(0, 1) + .build() + .build()} +write flush + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, -2) + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, 1, 2) + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .fetch() + .timestamp(newTimestamp) + .partition(0, 1, 2) + .build() + .build()} +write ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" +write flush diff --git a/specs/binding-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/kafka/streams/application/MergedIT.java b/specs/binding-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/kafka/streams/application/MergedIT.java index fccc447235..3cfd92161f 100644 --- a/specs/binding-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/kafka/streams/application/MergedIT.java +++ b/specs/binding-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/kafka/streams/application/MergedIT.java @@ -153,6 +153,24 @@ public void shouldFetchMergedMessageValueValid() throws Exception k3po.finish(); } + @Test + @Specification({ + "${app}/merged.fetch.from.parameterized.topic.value.valid/client", + "${app}/merged.fetch.from.parameterized.topic.value.valid/server"}) + public void shouldFetchMergedFromParameterizedTopicValid() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/merged.fetch.from.parameterized.topic.value.invalid/client", + "${app}/merged.fetch.from.parameterized.topic.value.invalid/server"}) + public void shouldFetchMergedFromParameterizedTopicInvalid() throws Exception + { + k3po.finish(); + } + @Test @Specification({ "${app}/merged.fetch.message.value.invalid/client", @@ -429,6 +447,24 @@ public void shouldFetchUnmergedMessageValueValid() throws Exception k3po.finish(); } + @Test + @Specification({ + "${app}/unmerged.fetch.from.parameterized.topic.value.valid/client", + "${app}/unmerged.fetch.from.parameterized.topic.value.valid/server"}) + public void shouldFetchUnmergedFromParameterizedTopicValid() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/unmerged.fetch.from.parameterized.topic.value.invalid/client", + "${app}/unmerged.fetch.from.parameterized.topic.value.invalid/server"}) + public void shouldFetchUnmergedFromParameterizedTopicInvalid() throws Exception + { + k3po.finish(); + } + @Test @Specification({ "${app}/unmerged.fetch.message.values/client", From 8c6bb21cd0d3a85def39a65b60a9b9163aefc923 Mon Sep 17 00:00:00 2001 From: bmaidics Date: Mon, 26 Feb 2024 21:16:43 +0100 Subject: [PATCH 12/25] Kafka asyncapi client (#804) --- incubator/binding-asyncapi.spec/NOTICE | 1 + incubator/binding-asyncapi.spec/pom.xml | 7 +- ...ent.secure.yaml => client.kafka.sasl.yaml} | 13 +- .../config/{client.yaml => client.kafka.yaml} | 4 +- .../asyncapi/config/kafka/asyncapi.yaml | 199 ++++++++++++ .../asyncapi/config/server.secure.yaml | 37 --- .../specs/binding/asyncapi/config/server.yaml | 26 -- .../schema/asyncapi.schema.patch.json | 10 + .../asyncapi/kafka/produce.message/client.rpt | 47 +++ .../asyncapi/kafka/produce.message/server.rpt | 44 +++ .../streams/kafka/produce.message/client.rpt | 45 +++ .../streams/kafka/produce.message/server.rpt | 43 +++ .../binding/asyncapi/config/SchemaTest.java | 16 + .../binding/asyncapi/streams/HttpIT.java | 49 +++ .../binding/asyncapi/streams/KafkaIT.java | 49 +++ .../asyncapi/streams/{mqtt => }/MqttIT.java | 2 +- .../asyncapi/streams/asyncapi/AsyncapiIT.java | 10 + incubator/binding-asyncapi/pom.xml | 13 +- .../config/AsyncapiOptionsConfig.java | 6 +- .../config/AsyncapiOptionsConfigBuilder.java | 11 +- ...AsyncapiClientCompositeBindingAdapter.java | 10 +- .../AsyncapiCompositeBindingAdapter.java | 10 +- .../internal/AsyncapiHttpProtocol.java | 6 + .../asyncapi/internal/AsyncapiProtocol.java | 48 +++ ...AsyncapiServerCompositeBindingAdapter.java | 10 +- .../internal/AyncapiKafkaProtocol.java | 187 +++++++++++ .../internal/AyncapiMqttProtocol.java | 10 +- .../config/AsyncapiBindingConfig.java | 4 +- .../config/AsyncapiOptionsConfigAdapter.java | 18 ++ .../internal/model/AsyncapiServer.java | 5 +- .../internal/view/AsyncapiServerView.java | 24 +- .../src/main/moditect/module-info.java | 1 + .../AsyncapiOptionsConfigAdapterTest.java | 301 +++++++++++++++++- .../internal/stream/client/AsyncapiIT.java | 15 + .../kafka/config/KafkaOptionsConfig.java | 14 +- .../config/KafkaOptionsConfigBuilder.java | 101 ++++++ .../binding/kafka/config/KafkaSaslConfig.java | 16 +- .../kafka/config/KafkaSaslConfigBuilder.java | 69 ++++ .../kafka/config/KafkaServerConfig.java | 15 +- .../config/KafkaServerConfigBuilder.java | 61 ++++ .../kafka/config/KafkaTopicConfig.java | 14 +- .../kafka/config/KafkaTopicConfigBuilder.java | 94 ++++++ .../config/KafkaOptionsConfigAdapter.java | 37 +-- .../config/KafkaTopicConfigAdapter.java | 26 +- .../stream/KafkaClientGroupFactory.java | 5 +- .../stream/KafkaClientMetaFactory.java | 2 +- .../KafkaClientOffsetCommitFactory.java | 5 +- .../stream/KafkaClientOffsetFetchFactory.java | 5 +- .../config/KafkaOptionsConfigAdapterTest.java | 76 +++-- .../kafka/schema/kafka.schema.patch.json | 155 +++++++-- 50 files changed, 1780 insertions(+), 196 deletions(-) rename incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/{client.secure.yaml => client.kafka.sasl.yaml} (81%) rename incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/{client.yaml => client.kafka.yaml} (94%) create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/asyncapi.yaml delete mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.secure.yaml delete mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/HttpIT.java create mode 100644 incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/KafkaIT.java rename incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/{mqtt => }/MqttIT.java (96%) create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java create mode 100644 runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfigBuilder.java create mode 100644 runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfigBuilder.java create mode 100644 runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfigBuilder.java create mode 100644 runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfigBuilder.java diff --git a/incubator/binding-asyncapi.spec/NOTICE b/incubator/binding-asyncapi.spec/NOTICE index 6c158382cf..86a7deb7c7 100644 --- a/incubator/binding-asyncapi.spec/NOTICE +++ b/incubator/binding-asyncapi.spec/NOTICE @@ -17,6 +17,7 @@ This project includes: Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception org.leadpony.justify under The Apache Software License, Version 2.0 zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-kafka.spec under The Apache Software License, Version 2.0 zilla::specs::binding-mqtt.spec under The Apache Software License, Version 2.0 zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 zilla::specs::engine.spec under The Apache Software License, Version 2.0 diff --git a/incubator/binding-asyncapi.spec/pom.xml b/incubator/binding-asyncapi.spec/pom.xml index 66d07acf09..592757d893 100644 --- a/incubator/binding-asyncapi.spec/pom.xml +++ b/incubator/binding-asyncapi.spec/pom.xml @@ -51,6 +51,11 @@ binding-http.spec ${project.version} + + ${project.groupId} + binding-kafka.spec + ${project.version} + junit junit @@ -88,7 +93,7 @@ flyweight-maven-plugin ${project.version} - core http mqtt asyncapi + core http mqtt kafka asyncapi io.aklivity.zilla.specs.binding.asyncapi.internal.types diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml similarity index 81% rename from incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.secure.yaml rename to incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml index 47ebc84ec3..2d31055aee 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.secure.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml @@ -26,16 +26,21 @@ bindings: vault: test0 options: specs: - - mqtt/asyncapi.yaml + - kafka/asyncapi.yaml tcp: host: localhost port: - - 7183 + - 9092 tls: trust: - serverca trustcacerts: true sni: - - mqtt.example.net + - kafka.example.net alpn: - - mqtt + - kafka + kafka: + sasl: + mechanism: plain + username: username + password: password diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml similarity index 94% rename from incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.yaml rename to incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml index 00b2ea5605..3570121f20 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml @@ -22,8 +22,8 @@ bindings: kind: client options: specs: - - mqtt/asyncapi.yaml + - kafka/asyncapi.yaml tcp: host: localhost port: - - 7183 + - 9092 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/asyncapi.yaml new file mode 100644 index 0000000000..1c7b1bf0df --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/asyncapi.yaml @@ -0,0 +1,199 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +asyncapi: 3.0.0 +info: + title: Streetlights Kafka API + version: 1.0.0 + description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n" + license: + name: Apache 2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0' +defaultContentType: application/json +servers: + scram-connections: + host: 'test.mykafkacluster.org:18092' + protocol: kafka-secure + description: Test broker secured with scramSha256 + security: + - $ref: '#/components/securitySchemes/saslScram' + tags: + - name: 'env:test-scram' + description: >- + This environment is meant for running internal tests through + scramSha256 + - name: 'kind:remote' + description: This server is a remote server. Not exposed by the application + - name: 'visibility:private' + description: This resource is private and only available to certain users +channels: + lightingMeasured: + address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured' + messages: + lightMeasured: + $ref: '#/components/messages/lightMeasured' + description: The topic on which measured values may be produced and consumed. + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightTurnOn: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.on' + messages: + turnOn: + $ref: '#/components/messages/turnOnOff' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightTurnOff: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.off' + messages: + turnOff: + $ref: '#/components/messages/turnOnOff' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightsDim: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.dim' + messages: + dimLight: + $ref: '#/components/messages/dimLight' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' +operations: + receiveLightMeasurement: + action: receive + channel: + $ref: '#/channels/lightingMeasured' + summary: >- + Inform about environmental lighting conditions of a particular + streetlight. + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightingMeasured/messages/lightMeasured' + turnOn: + action: send + channel: + $ref: '#/channels/lightTurnOn' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightTurnOn/messages/turnOn' + turnOff: + action: send + channel: + $ref: '#/channels/lightTurnOff' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightTurnOff/messages/turnOff' + dimLight: + action: send + channel: + $ref: '#/channels/lightsDim' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightsDim/messages/dimLight' +components: + messages: + lightMeasured: + name: lightMeasured + title: Light measured + summary: >- + Inform about environmental lighting conditions of a particular + streetlight. + contentType: application/json + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/lightMeasuredPayload' + turnOnOff: + name: turnOnOff + title: Turn on/off + summary: Command a particular streetlight to turn the lights on or off. + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/turnOnOffPayload' + dimLight: + name: dimLight + title: Dim light + summary: Command a particular streetlight to dim the lights. + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/dimLightPayload' + schemas: + lightMeasuredPayload: + type: object + properties: + lumens: + type: integer + minimum: 0 + description: Light intensity measured in lumens. + sentAt: + $ref: '#/components/schemas/sentAt' + turnOnOffPayload: + type: object + properties: + command: + type: string + enum: + - 'on' + - 'off' + description: Whether to turn on or off the light. + sentAt: + $ref: '#/components/schemas/sentAt' + dimLightPayload: + type: object + properties: + percentage: + type: integer + description: Percentage to which the light should be dimmed to. + minimum: 0 + maximum: 100 + sentAt: + $ref: '#/components/schemas/sentAt' + sentAt: + type: string + format: date-time + description: Date and time when the message was sent. + securitySchemes: + saslScram: + type: scramSha256 + description: Provide your username and password for SASL/SCRAM authentication + parameters: + streetlightId: + description: The ID of the streetlight. + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + operationTraits: + kafka: + bindings: + kafka: + clientId: + type: string + enum: + - my-app-id diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.secure.yaml deleted file mode 100644 index 25e5d1bcd5..0000000000 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.secure.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# -# Copyright 2021-2023 Aklivity Inc. -# -# Aklivity licenses this file to you under the Apache License, -# version 2.0 (the "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - ---- -name: test -vaults: - test0: - type: test -bindings: - composite0: - type: asyncapi - kind: server - vault: test0 - options: - specs: - - mqtt/asyncapi.yaml - tls: - keys: - - localhost - sni: - - mqtt.example.net - alpn: - - mqtt - exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.yaml deleted file mode 100644 index 8e65b8c21c..0000000000 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright 2021-2023 Aklivity Inc. -# -# Aklivity licenses this file to you under the Apache License, -# version 2.0 (the "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at: -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - ---- -name: test -bindings: - composite0: - type: asyncapi - kind: server - options: - specs: - - mqtt/asyncapi.yaml - exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json index 4cb1640a3a..d06a11b2c7 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json @@ -55,6 +55,16 @@ "authorization": "$defs/options/binding/http/authorization" }, "additionalProperties": false + }, + "kafka": + { + "title": "Kafka", + "type": "object", + "properties": + { + "sasl": "$defs/options/binding/kafka/sasl" + }, + "additionalProperties": false } }, "additionalProperties": false diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/client.rpt new file mode 100644 index 0000000000..dbd8c02087 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/client.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .timestamp(newTimestamp) + .partition(0, 1) + .build() + .build()} +write "Hello, world" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/server.rpt new file mode 100644 index 0000000000..e725f5ad6e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/server.rpt @@ -0,0 +1,44 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .partition(0, 1) + .build() + .build()} +read "Hello, world" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/client.rpt new file mode 100644 index 0000000000..1e13239fe6 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/client.rpt @@ -0,0 +1,45 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/kafka" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .timestamp(newTimestamp) + .partition(0, 1) + .build() + .build()} +write "Hello, world" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt new file mode 100644 index 0000000000..303bd53b17 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 16 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .partition(0, 1) + .build() + .build()} +read "Hello, world" diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java index eb2f207fe5..71f3551cd9 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java @@ -97,4 +97,20 @@ public void shouldValidateHttpSecureServer() assertThat(config, not(nullValue())); } + + @Test + public void shouldValidateKafkaClient() + { + JsonObject config = schema.validate("client.kafka.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateKafkaClientSasl() + { + JsonObject config = schema.validate("client.kafka.sasl.yaml"); + + assertThat(config, not(nullValue())); + } } diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/HttpIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/HttpIT.java new file mode 100644 index 0000000000..a12163b237 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/HttpIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class HttpIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + + @Test + @Specification({ + "${http}/create.pet/client", + "${http}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/KafkaIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/KafkaIT.java new file mode 100644 index 0000000000..9e43d077af --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/KafkaIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class KafkaIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("kafka", "io/aklivity/zilla/specs/binding/asyncapi/streams/kafka"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + + @Test + @Specification({ + "${kafka}/produce.message/client", + "${kafka}/produce.message/server" + }) + public void shouldProduceMessage() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/MqttIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/MqttIT.java similarity index 96% rename from incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/MqttIT.java rename to incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/MqttIT.java index d4bc19c18b..51ac9a8bec 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/MqttIT.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/MqttIT.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.specs.binding.asyncapi.streams.mqtt; +package io.aklivity.zilla.specs.binding.asyncapi.streams; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.rules.RuleChain.outerRule; diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java index 96d6a5be38..bfefedfefd 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java @@ -55,4 +55,14 @@ public void shouldCreatePet() throws Exception { k3po.finish(); } + + @Test + @Specification({ + "${asyncapi}/kafka/produce.message/client", + "${asyncapi}/kafka/produce.message/server" + }) + public void shouldProduceMessage() throws Exception + { + k3po.finish(); + } } diff --git a/incubator/binding-asyncapi/pom.xml b/incubator/binding-asyncapi/pom.xml index 40bbb982ba..eac2b62f25 100644 --- a/incubator/binding-asyncapi/pom.xml +++ b/incubator/binding-asyncapi/pom.xml @@ -61,6 +61,12 @@ ${project.version} provided + + io.aklivity.zilla + binding-kafka + ${project.version} + provided + io.aklivity.zilla binding-tcp @@ -96,6 +102,11 @@ jackson-dataformat-yaml 2.16.1 + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + ${project.groupId} engine @@ -157,7 +168,7 @@ flyweight-maven-plugin ${project.version} - core mqtt http asyncapi + core mqtt http kafka asyncapi io.aklivity.zilla.runtime.binding.asyncapi.internal.types diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java index daa394055e..e6f6132177 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java @@ -18,6 +18,7 @@ import java.util.function.Function; import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; @@ -28,6 +29,7 @@ public final class AsyncapiOptionsConfig extends OptionsConfig public final TcpOptionsConfig tcp; public final TlsOptionsConfig tls; public final HttpOptionsConfig http; + public final KafkaOptionsConfig kafka; public static AsyncapiOptionsConfigBuilder builder() { @@ -44,11 +46,13 @@ public AsyncapiOptionsConfig( List specs, TcpOptionsConfig tcp, TlsOptionsConfig tls, - HttpOptionsConfig http) + HttpOptionsConfig http, + KafkaOptionsConfig kafka) { this.specs = specs; this.http = http; this.tcp = tcp; this.tls = tls; + this.kafka = kafka; } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java index 1dfbddaceb..2d3b02b3a4 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java @@ -18,6 +18,7 @@ import java.util.function.Function; import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; @@ -31,6 +32,7 @@ public final class AsyncapiOptionsConfigBuilder extends ConfigBuilder mapper) @@ -73,10 +75,17 @@ public AsyncapiOptionsConfigBuilder http( return this; } + public AsyncapiOptionsConfigBuilder kafka( + KafkaOptionsConfig kafka) + { + this.kafka = kafka; + return this; + } + @Override public T build() { - return mapper.apply(new AsyncapiOptionsConfig(specs, tcp, tls, http)); + return mapper.apply(new AsyncapiOptionsConfig(specs, tcp, tls, http, kafka)); } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java index 3892e398c7..f725f2b98d 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java @@ -19,14 +19,12 @@ import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; -import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiView; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; public class AsyncapiClientCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi { - @Override public String type() { @@ -40,22 +38,24 @@ public BindingConfig adapt( AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; AsyncapiConfig asyncapiConfig = options.specs.get(0); this.asyncapi = asyncapiConfig.asyncapi; - AsyncapiView asyncapiView = AsyncapiView.of(asyncapi); //TODO: add composite for all servers AsyncapiServerView firstServer = AsyncapiServerView.of(asyncapi.servers.entrySet().iterator().next().getValue()); this.qname = binding.qname; this.qvault = binding.qvault; this.protocol = resolveProtocol(firstServer.protocol(), options); - int[] compositeSecurePorts = asyncapiView.resolvePortsForScheme(protocol.secureScheme); - this.isTlsEnabled = compositeSecurePorts != null; + this.compositePorts = protocol.resolvePorts(); + this.isTlsEnabled = protocol.isSecure(); + return BindingConfig.builder(binding) .composite() .name(String.format("%s.%s", qname, "$composite")) + .inject(protocol::injectProtocolClientCache) .binding() .name(String.format("%s_client0", protocol.scheme)) .type(protocol.scheme) .kind(CLIENT) + .inject(protocol::injectProtocolClientOptions) .exit(isTlsEnabled ? "tls_client0" : "tcp_client0") .build() .inject(n -> injectTlsClient(n, options)) diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java index 02a7ccf455..1c0e781af7 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java @@ -26,6 +26,8 @@ public class AsyncapiCompositeBindingAdapter protected Asyncapi asyncapi; protected boolean isTlsEnabled; + protected int[] allPorts; + protected int[] compositePorts; protected AsyncapiProtocol protocol; protected String qname; protected String qvault; @@ -35,7 +37,7 @@ protected AsyncapiProtocol resolveProtocol( String protocolName, AsyncapiOptionsConfig options) { - Pattern pattern = Pattern.compile("(http|mqtt)"); + Pattern pattern = Pattern.compile("(http|mqtt|kafka)"); Matcher matcher = pattern.matcher(protocolName); AsyncapiProtocol protocol = null; if (matcher.find()) @@ -46,7 +48,11 @@ protected AsyncapiProtocol resolveProtocol( protocol = new AsyncapiHttpProtocol(qname, asyncapi, options); break; case "mqtt": - protocol = new AyncapiMqttProtocol(qname, asyncapi, options); + protocol = new AyncapiMqttProtocol(qname, asyncapi); + break; + case "kafka": + case "kafka-secure": + protocol = new AyncapiKafkaProtocol(qname, asyncapi, options, protocolName); break; } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java index f01ba1b949..1663d73a62 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java @@ -118,6 +118,12 @@ public BindingConfigBuilder injectProtocolServerRoutes( return binding; } + @Override + protected boolean isSecure() + { + return scheme.equals(SECURE_SCHEME); + } + private HttpOptionsConfigBuilder injectHttpServerOptions( HttpOptionsConfigBuilder options) { diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java index 52d678e7dd..ea896536cb 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java @@ -14,6 +14,9 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal; +import static java.util.Objects.requireNonNull; + +import java.net.URI; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -21,8 +24,10 @@ import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; public abstract class AsyncapiProtocol { @@ -54,6 +59,18 @@ public abstract BindingConfigBuilder injectProtocolServerOptions( public abstract BindingConfigBuilder injectProtocolServerRoutes( BindingConfigBuilder binding); + public NamespaceConfigBuilder injectProtocolClientCache( + NamespaceConfigBuilder namespace) + { + return namespace; + } + + public BindingConfigBuilder injectProtocolClientOptions( + BindingConfigBuilder binding) + { + return binding; + } + protected CatalogedConfigBuilder injectJsonSchemas( CatalogedConfigBuilder cataloged, Map messages, @@ -92,4 +109,35 @@ protected boolean hasJsonContentType() } return contentType != null && jsonContentType.reset(contentType).matches(); } + + protected abstract boolean isSecure(); + + protected int[] resolvePorts() + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + protected URI findFirstServerUrlWithScheme( + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (String key : asyncApi.servers.keySet()) + { + AsyncapiServerView server = AsyncapiServerView.of(asyncApi.servers.get(key)); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java index ad994b3eee..970054fc70 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java @@ -56,10 +56,8 @@ public BindingConfig adapt( this.qvault = binding.qvault; this.protocol = resolveProtocol(firstServer.protocol(), options); int[] allPorts = asyncapiView.resolveAllPorts(); - this.compositePorts = asyncapiView.resolvePortsForScheme(protocol.scheme); - this.compositeSecurePorts = asyncapiView.resolvePortsForScheme(protocol.secureScheme); - this.isPlainEnabled = compositePorts != null; - this.isTlsEnabled = compositeSecurePorts != null; + this.compositePorts = protocol.resolvePorts(); + this.isTlsEnabled = protocol.isSecure(); return BindingConfig.builder(binding) .composite() @@ -90,7 +88,7 @@ public BindingConfig adapt( private BindingConfigBuilder injectPlainTcpRoute( BindingConfigBuilder binding) { - if (isPlainEnabled) + if (!isTlsEnabled) { binding .route() @@ -111,7 +109,7 @@ private BindingConfigBuilder injectTlsTcpRoute( binding .route() .when(TcpConditionConfig::builder) - .ports(compositeSecurePorts) + .ports(compositePorts) .build() .exit("tls_server0") .build(); diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java new file mode 100644 index 0000000000..1b54d2d687 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java @@ -0,0 +1,187 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import java.net.URI; +import java.util.Map; +import java.util.stream.Collectors; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public class AyncapiKafkaProtocol extends AsyncapiProtocol +{ + private static final String SCHEME = "kafka"; + private static final String SECURE_SCHEME = ""; + private static final String SECURE_PROTOCOL = "kafka-secure"; + private final String protocol; + private final KafkaSaslConfig sasl; + + public AyncapiKafkaProtocol( + String qname, + Asyncapi asyncApi, + AsyncapiOptionsConfig options, + String protocol) + { + super(qname, asyncApi, SCHEME, SECURE_SCHEME); + this.protocol = protocol; + this.sasl = options.kafka != null ? options.kafka.sasl : null; + } + + @Override + public NamespaceConfigBuilder injectProtocolClientCache( + NamespaceConfigBuilder namespace) + { + return namespace + .binding() + .name("kafka_cache_client0") + .type("kafka") + .kind(KindConfig.CACHE_CLIENT) + .exit("kafka_cache_server0") + .build() + .binding() + .name("kafka_cache_server0") + .type("kafka") + .kind(KindConfig.CACHE_SERVER) + .exit("kafka_client0") + .build(); + } + + @Override + public BindingConfigBuilder injectProtocolClientOptions( + BindingConfigBuilder binding) + { + return sasl == null ? binding : + binding.options(KafkaOptionsConfig::builder) + .sasl(KafkaSaslConfig::builder) + .mechanism(sasl.mechanism) + .username(sasl.username) + .password(sasl.password) + .build() + .inject(this::injectKafkaServerOptions) + //.inject(this::injectKafkaBootstrapOptions) + .inject(this::injectKafkaTopicOptions) + .build(); + } + + @Override + public BindingConfigBuilder injectProtocolServerOptions( + BindingConfigBuilder binding) + { + return binding; + } + + @Override + public BindingConfigBuilder injectProtocolServerRoutes( + BindingConfigBuilder binding) + { + return binding; + } + + @Override + protected boolean isSecure() + { + return protocol.equals(SECURE_PROTOCOL); + } + + private KafkaOptionsConfigBuilder injectKafkaServerOptions( + KafkaOptionsConfigBuilder options) + { + return options.servers(asyncApi.servers.values().stream().map(s -> + { + final URI serverUrl = AsyncapiServerView.of(s).url(); + return KafkaServerConfig.builder() + .host(serverUrl.getHost()) + .port(serverUrl.getPort()) + .build(); + }).collect(Collectors.toList())); + } + + private KafkaOptionsConfigBuilder injectKafkaTopicOptions( + KafkaOptionsConfigBuilder options) + { + for (String name : asyncApi.operations.keySet()) + { + AsyncapiOperation operation = asyncApi.operations.get(name); + AsyncapiChannelView channel = AsyncapiChannelView.of(asyncApi.channels, operation.channel); + String topic = channel.address(); + + if (channel.messages() != null && !channel.messages().isEmpty() || + channel.parameters() != null && !channel.parameters().isEmpty()) + { + options + .topic(KafkaTopicConfig::builder) + .name(topic) + .inject(topicConfig -> injectValue(topicConfig, channel.messages())) + .build() + .build(); + } + } + return options; + } + + private KafkaTopicConfigBuilder injectValue( + KafkaTopicConfigBuilder topic, + Map messages) + { + if (messages != null) + { + if (hasJsonContentType()) + { + topic + .value(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .inject(catalog -> injectSchemas(catalog, messages)) + .build() + .build(); + } + } + return topic; + } + + private CatalogedConfigBuilder injectSchemas( + CatalogedConfigBuilder catalog, + Map messages) + { + for (String name : messages.keySet()) + { + AsyncapiMessageView message = AsyncapiMessageView.of(asyncApi.components.messages, messages.get(name)); + String subject = message.refKey() != null ? message.refKey() : name; + catalog + .schema() + .subject(subject) + .build() + .build(); + } + return catalog; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java index c9f9dee1b7..685544ab0e 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java @@ -18,7 +18,6 @@ import java.util.Map; -import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiChannel; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; @@ -34,8 +33,7 @@ public class AyncapiMqttProtocol extends AsyncapiProtocol public AyncapiMqttProtocol( String qname, - Asyncapi asyncApi, - AsyncapiOptionsConfig options) + Asyncapi asyncApi) { super(qname, asyncApi, SCHEME, SECURE_SCHEME); } @@ -92,4 +90,10 @@ public BindingConfigBuilder injectProtocolServerRoutes( } return binding; } + + @Override + protected boolean isSecure() + { + return scheme.equals(SECURE_SCHEME); + } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java index 2dc25ae748..c85f74fad3 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java @@ -69,7 +69,7 @@ public AsyncapiBindingConfig( this.resolvedIds = binding.composites.stream() .map(c -> c.bindings) .flatMap(List::stream) - .filter(b -> b.type.equals("mqtt") || b.type.equals("http")) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka")) .collect(of( () -> new Long2LongHashMap(-1), (m, r) -> m.put(0L, r.id), //TODO: populate proper apiId @@ -80,7 +80,7 @@ public AsyncapiBindingConfig( binding.composites.stream() .map(c -> c.bindings) .flatMap(List::stream) - .filter(b -> b.type.equals("mqtt") || b.type.equals("http")) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka")) .forEach(b -> this.composites.put(NamespacedId.namespaceId(b.id), b.type)); this.paths = new Object2ObjectHashMap<>(); diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java index 1df06bf50e..2d24c4bf54 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java @@ -47,6 +47,7 @@ import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; @@ -61,10 +62,12 @@ public final class AsyncapiOptionsConfigAdapter implements OptionsConfigAdapterS private static final String TCP_NAME = "tcp"; private static final String TLS_NAME = "tls"; private static final String HTTP_NAME = "http"; + private static final String KAFKA_NAME = "kafka"; private OptionsConfigAdapter tcpOptions; private OptionsConfigAdapter tlsOptions; private OptionsConfigAdapter httpOptions; + private OptionsConfigAdapter kafkaOptions; private Function readURL; public Kind kind() @@ -111,6 +114,12 @@ public JsonObject adaptToJson( object.add(HTTP_NAME, httpOptions.adaptToJson(http)); } + if (asyncapiOptions.kafka != null) + { + final KafkaOptionsConfig kafka = asyncapiOptions.kafka; + object.add(KAFKA_NAME, kafkaOptions.adaptToJson(kafka)); + } + return object.build(); } @@ -146,6 +155,13 @@ public OptionsConfig adaptFromJson( asyncapiOptions.http(httpOptions); } + if (object.containsKey(KAFKA_NAME)) + { + final JsonObject kafka = object.getJsonObject(KAFKA_NAME); + final KafkaOptionsConfig kafkaOptions = (KafkaOptionsConfig) this.kafkaOptions.adaptFromJson(kafka); + asyncapiOptions.kafka(kafkaOptions); + } + return asyncapiOptions.build(); } @@ -160,6 +176,8 @@ public void adaptContext( this.tlsOptions.adaptType("tls"); this.httpOptions = new OptionsConfigAdapter(Kind.BINDING, context); this.httpOptions.adaptType("http"); + this.kafkaOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.kafkaOptions.adaptType("kafka"); } private List asListAsyncapis( diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java index 01d390d4a6..bcf34983e3 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java @@ -14,12 +14,11 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; -import java.util.List; -import java.util.Map; +import jakarta.json.JsonArray; public class AsyncapiServer { public String host; public String protocol; - public List>> security; + public JsonArray security; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java index 7f7c95d78d..e1df7b7158 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java @@ -14,15 +14,22 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; +import static org.agrona.LangUtil.rethrowUnchecked; + import java.net.URI; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiServer; public final class AsyncapiServerView { private final AsyncapiServer server; + private final ObjectMapper objectMapper = new ObjectMapper(); public URI url() { @@ -31,7 +38,22 @@ public URI url() public List>> security() { - return server.security; + List>> security = null; + if (server.security != null) + { + try + { + security = objectMapper.readValue(server.security.toString(), new TypeReference<>() + { + }); + } + catch (JsonProcessingException e) + { + rethrowUnchecked(e); + } + } + + return security; } public String protocol() diff --git a/incubator/binding-asyncapi/src/main/moditect/module-info.java b/incubator/binding-asyncapi/src/main/moditect/module-info.java index db4f24ab6a..6c2056298b 100644 --- a/incubator/binding-asyncapi/src/main/moditect/module-info.java +++ b/incubator/binding-asyncapi/src/main/moditect/module-info.java @@ -17,6 +17,7 @@ requires io.aklivity.zilla.runtime.engine; requires io.aklivity.zilla.runtime.binding.mqtt; requires io.aklivity.zilla.runtime.binding.http; + requires io.aklivity.zilla.runtime.binding.kafka; requires io.aklivity.zilla.runtime.binding.tcp; requires io.aklivity.zilla.runtime.binding.tls; requires io.aklivity.zilla.runtime.catalog.inline; diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java index f2bb6d64c0..1f73927d8b 100644 --- a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java @@ -32,7 +32,6 @@ import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; @@ -43,6 +42,8 @@ import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; @@ -58,14 +59,14 @@ public class AsyncapiOptionsConfigAdapterTest private ConfigAdapterContext context; private Jsonb jsonb; - @Before - public void initJson() throws IOException + public void initJson( + String asyncapiConfig) throws IOException { try (InputStream resource = AsyncapiSpecs.class - .getResourceAsStream("config/mqtt/asyncapi.yaml")) + .getResourceAsStream("config/" + asyncapiConfig)) { String content = new String(resource.readAllBytes(), UTF_8); - Mockito.doReturn(content).when(context).readURL("mqtt/asyncapi.yaml"); + Mockito.doReturn(content).when(context).readURL(asyncapiConfig); OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); adapter.adaptType("asyncapi"); @@ -76,13 +77,14 @@ public void initJson() throws IOException } @Test - public void shouldReadOptions() + public void shouldReadOptionsMqtt() throws IOException { + initJson("mqtt/asyncapi.yaml"); String text = "{" + "\"specs\":" + "[" + - "\"mqtt/asyncapi.yaml\"" + + "\"mqtt/asyncapi.yaml\"," + "]," + "\"tcp\":" + "{" + @@ -108,6 +110,15 @@ public void shouldReadOptions() "[" + "\"mqtt\"" + "]" + + "}," + + "\"kafka\":" + + "{" + + "\"sasl\":" + + "{" + + "\"mechanism\":\"plain\"," + + "\"username\":\"username\"," + + "\"password\":\"password\"" + + "}" + "}" + "}"; @@ -124,11 +135,15 @@ public void shouldReadOptions() assertThat(options.tls.trustcacerts, equalTo(true)); assertThat(options.tls.sni, equalTo(asList("mqtt.example.net"))); assertThat(options.tls.alpn, equalTo(asList("mqtt"))); + assertThat(options.kafka.sasl.mechanism, equalTo("plain")); + assertThat(options.kafka.sasl.username, equalTo("username")); + assertThat(options.kafka.sasl.password, equalTo("password")); } @Test - public void shouldWriteOptions() + public void shouldWriteOptionsMqtt() throws IOException { + initJson("mqtt/asyncapi.yaml"); List specs = new ArrayList<>(); specs.add(new AsyncapiConfig("mqtt/asyncapi.yaml", new Asyncapi())); @@ -147,6 +162,13 @@ public void shouldWriteOptions() .alpn(asList("mqtt")) .trustcacerts(true) .build()) + .kafka(KafkaOptionsConfig.builder() + .sasl(KafkaSaslConfig.builder() + .mechanism("plain") + .username("username") + .password("password") + .build()) + .build()) .build(); String text = jsonb.toJson(options); @@ -182,7 +204,270 @@ public void shouldWriteOptions() "[" + "\"mqtt\"" + "]" + + "}," + + "\"kafka\":" + + "{" + + "\"sasl\":" + + "{" + + "\"mechanism\":\"plain\"," + + "\"username\":\"username\"," + + "\"password\":\"password\"" + + "}" + + "}" + + "}")); + } + + @Test + public void shouldReadOptionsKafka() throws IOException + { + initJson("kafka/asyncapi.yaml"); + String text = + "{" + + "\"specs\":" + + "[" + + "\"kafka/asyncapi.yaml\"," + + "]," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":9092" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"kafka.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"kafka\"" + + "]" + + "}," + + "\"kafka\":" + + "{" + + "\"sasl\":" + + "{" + + "\"mechanism\":\"plain\"," + + "\"username\":\"username\"," + + "\"password\":\"password\"" + + "}" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + assertThat(options, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.get(0); + assertThat(asyncapi.location, equalTo("kafka/asyncapi.yaml")); + assertThat(asyncapi.asyncapi, instanceOf(Asyncapi.class)); + assertThat(options.tcp.host, equalTo("localhost")); + assertThat(options.tcp.ports, equalTo(new int[] { 9092 })); + assertThat(options.tls.keys, equalTo(asList("localhost"))); + assertThat(options.tls.trust, equalTo(asList("serverca"))); + assertThat(options.tls.trustcacerts, equalTo(true)); + assertThat(options.tls.sni, equalTo(asList("kafka.example.net"))); + assertThat(options.tls.alpn, equalTo(asList("kafka"))); + assertThat(options.kafka.sasl.mechanism, equalTo("plain")); + assertThat(options.kafka.sasl.username, equalTo("username")); + assertThat(options.kafka.sasl.password, equalTo("password")); + } + + @Test + public void shouldWriteOptionsHttp() throws IOException + { + initJson("http/asyncapi.yaml"); + List specs = new ArrayList<>(); + specs.add(new AsyncapiConfig("http/asyncapi.yaml", new Asyncapi())); + + + AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() + .inject(Function.identity()) + .specs(specs) + .tcp(TcpOptionsConfig.builder() + .host("localhost") + .ports(new int[] { 7080 }) + .build()) + .tls(TlsOptionsConfig.builder() + .keys(asList("localhost")) + .trust(asList("serverca")) + .sni(asList("http.example.net")) + .alpn(asList("http")) + .trustcacerts(true) + .build()) + .build(); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"specs\":" + + "[" + + "\"http/asyncapi.yaml\"" + + "]," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":7080" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"http.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"http\"" + + "]" + "}" + "}")); } + + @Test + public void shouldReadOptionsHttp() throws IOException + { + initJson("http/asyncapi.yaml"); + String text = + "{" + + "\"specs\":" + + "[" + + "\"http/asyncapi.yaml\"," + + "]," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":7080" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"http.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"http\"" + + "]" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + assertThat(options, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.get(0); + assertThat(asyncapi.location, equalTo("http/asyncapi.yaml")); + assertThat(asyncapi.asyncapi, instanceOf(Asyncapi.class)); + assertThat(options.tcp.host, equalTo("localhost")); + assertThat(options.tcp.ports, equalTo(new int[] { 7080 })); + assertThat(options.tls.keys, equalTo(asList("localhost"))); + assertThat(options.tls.trust, equalTo(asList("serverca"))); + assertThat(options.tls.trustcacerts, equalTo(true)); + assertThat(options.tls.sni, equalTo(asList("http.example.net"))); + assertThat(options.tls.alpn, equalTo(asList("http"))); + } + + @Test + public void shouldWriteOptionsKafka() throws IOException + { + initJson("kafka/asyncapi.yaml"); + List specs = new ArrayList<>(); + specs.add(new AsyncapiConfig("kafka/asyncapi.yaml", new Asyncapi())); + + + AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() + .inject(Function.identity()) + .specs(specs) + .tcp(TcpOptionsConfig.builder() + .host("localhost") + .ports(new int[] { 9092 }) + .build()) + .tls(TlsOptionsConfig.builder() + .keys(asList("localhost")) + .trust(asList("serverca")) + .sni(asList("kafka.example.net")) + .alpn(asList("kafka")) + .trustcacerts(true) + .build()) + .kafka(KafkaOptionsConfig.builder() + .sasl(KafkaSaslConfig.builder() + .mechanism("plain") + .username("username") + .password("password") + .build()) + .build()) + .build(); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"specs\":" + + "[" + + "\"kafka/asyncapi.yaml\"" + + "]," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":9092" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"kafka.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"kafka\"" + + "]" + + "}," + + "\"kafka\":" + + "{" + + "\"sasl\":" + + "{" + + "\"mechanism\":\"plain\"," + + "\"username\":\"username\"," + + "\"password\":\"password\"" + + "}" + + "}" + + "}")); + } } diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java index e02c9243bf..1579c36616 100644 --- a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java @@ -37,6 +37,7 @@ public class AsyncapiIT private final K3poRule k3po = new K3poRule() .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt") .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http") + .addScriptRoot("kafka", "io/aklivity/zilla/specs/binding/asyncapi/streams/kafka") .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); @@ -48,6 +49,7 @@ public class AsyncapiIT .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config") .external("mqtt0") .external("http0") + .external("kafka0") .clean(); @Rule @@ -78,4 +80,17 @@ public void shouldCreatePet() throws Exception { k3po.finish(); } + + @Test + @Configuration("client.kafka.yaml") + @Specification({ + "${asyncapi}/kafka/produce.message/client", + "${kafka}/produce.message/server" + }) + @Configure(name = ASYNCAPI_TARGET_ROUTE_ID_NAME, value = "4294967300") + @ScriptProperty("serverAddress \"zilla://streams/kafka0\"") + public void shouldProduceMessage() throws Exception + { + k3po.finish(); + } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfig.java index bba5a40275..e597ce09ac 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfig.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Stream; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; @@ -31,7 +32,18 @@ public final class KafkaOptionsConfig extends OptionsConfig public final List servers; public final KafkaSaslConfig sasl; - public KafkaOptionsConfig( + public static KafkaOptionsConfigBuilder builder() + { + return new KafkaOptionsConfigBuilder<>(KafkaOptionsConfig.class::cast); + } + + public static KafkaOptionsConfigBuilder builder( + Function mapper) + { + return new KafkaOptionsConfigBuilder<>(mapper); + } + + KafkaOptionsConfig( List bootstrap, List topics, List servers, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfigBuilder.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfigBuilder.java new file mode 100644 index 0000000000..86ae0015a6 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfigBuilder.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class KafkaOptionsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private List bootstrap; + private List topics; + private List servers; + private KafkaSaslConfig sasl; + + KafkaOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + this.topics = new ArrayList<>(); + this.servers = new ArrayList<>(); + this.bootstrap = new ArrayList<>(); + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public KafkaOptionsConfigBuilder bootstrap( + List bootstrap) + { + this.bootstrap = bootstrap; + return this; + } + + public KafkaOptionsConfigBuilder topics( + List topics) + { + this.topics = topics; + return this; + } + + public KafkaOptionsConfigBuilder topic( + KafkaTopicConfig topic) + { + this.topics.add(topic); + return this; + } + + public , C>> C topic( + Function>, C> topic) + { + return topic.apply(this::topic); + } + + public KafkaOptionsConfigBuilder servers( + List servers) + { + this.servers = servers; + return this; + } + + public KafkaOptionsConfigBuilder sasl( + KafkaSaslConfig sasl) + { + this.sasl = sasl; + return this; + } + + public , C>> C sasl( + Function>, C> sasl) + { + return sasl.apply(this::sasl); + } + + @Override + public T build() + { + return mapper.apply(new KafkaOptionsConfig(bootstrap, topics, servers, sasl)); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfig.java index 4860089a0c..7ee7197122 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfig.java @@ -15,13 +15,27 @@ */ package io.aklivity.zilla.runtime.binding.kafka.config; + +import java.util.function.Function; + public class KafkaSaslConfig { public final String mechanism; public final String username; public final String password; - public KafkaSaslConfig( + public static KafkaSaslConfigBuilder builder() + { + return new KafkaSaslConfigBuilder<>(KafkaSaslConfig.class::cast); + } + + public static KafkaSaslConfigBuilder builder( + Function mapper) + { + return new KafkaSaslConfigBuilder<>(mapper); + } + + KafkaSaslConfig( String mechanism, String username, String password) diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfigBuilder.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfigBuilder.java new file mode 100644 index 0000000000..d0ab12dd06 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfigBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.config; + + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class KafkaSaslConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String mechanism; + private String username; + private String password; + + KafkaSaslConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public KafkaSaslConfigBuilder mechanism( + String mechanism) + { + this.mechanism = mechanism; + return this; + } + + public KafkaSaslConfigBuilder username( + String username) + { + this.username = username; + return this; + } + + public KafkaSaslConfigBuilder password( + String password) + { + this.password = password; + return this; + } + + @Override + public T build() + { + return mapper.apply(new KafkaSaslConfig(mechanism, username, password)); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfig.java index 8f605807fb..3b2dd559ca 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfig.java @@ -15,12 +15,25 @@ */ package io.aklivity.zilla.runtime.binding.kafka.config; +import java.util.function.Function; + public class KafkaServerConfig { public final String host; public final int port; - public KafkaServerConfig( + public static KafkaServerConfigBuilder builder() + { + return new KafkaServerConfigBuilder<>(KafkaServerConfig.class::cast); + } + + public static KafkaServerConfigBuilder builder( + Function mapper) + { + return new KafkaServerConfigBuilder<>(mapper); + } + + KafkaServerConfig( String host, int port) { diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfigBuilder.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfigBuilder.java new file mode 100644 index 0000000000..dca827666e --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfigBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.config; + + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class KafkaServerConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String host; + private int port; + + KafkaServerConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public KafkaServerConfigBuilder host( + String host) + { + this.host = host; + return this; + } + + public KafkaServerConfigBuilder port( + int port) + { + this.port = port; + return this; + } + + @Override + public T build() + { + return mapper.apply(new KafkaServerConfig(host, port)); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfig.java index 8eafe82740..679a743b15 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfig.java @@ -16,6 +16,7 @@ package io.aklivity.zilla.runtime.binding.kafka.config; import java.util.Objects; +import java.util.function.Function; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaDeltaType; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaOffsetType; @@ -29,7 +30,18 @@ public class KafkaTopicConfig public final ModelConfig key; public final ModelConfig value; - public KafkaTopicConfig( + public static KafkaTopicConfigBuilder builder() + { + return new KafkaTopicConfigBuilder<>(KafkaTopicConfig.class::cast); + } + + public static KafkaTopicConfigBuilder builder( + Function mapper) + { + return new KafkaTopicConfigBuilder<>(mapper); + } + + KafkaTopicConfig( String name, KafkaOffsetType defaultOffset, KafkaDeltaType deltaType, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfigBuilder.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfigBuilder.java new file mode 100644 index 0000000000..34a423b225 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfigBuilder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.config; + + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaDeltaType; +import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaOffsetType; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; + +public final class KafkaTopicConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String name; + private KafkaOffsetType defaultOffset; + private KafkaDeltaType deltaType; + private ModelConfig key; + private ModelConfig value; + + KafkaTopicConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public KafkaTopicConfigBuilder name( + String name) + { + this.name = name; + return this; + } + + public KafkaTopicConfigBuilder defaultOffset( + KafkaOffsetType defaultOffset) + { + this.defaultOffset = defaultOffset; + return this; + } + + public KafkaTopicConfigBuilder deltaType( + KafkaDeltaType deltaType) + { + this.deltaType = deltaType; + return this; + } + + public KafkaTopicConfigBuilder key( + ModelConfig key) + { + this.key = key; + return this; + } + + public KafkaTopicConfigBuilder value( + ModelConfig value) + { + this.value = value; + return this; + } + + public , C>> C value( + Function>, C> value) + { + return value.apply(this::value); + } + + @Override + public T build() + { + return mapper.apply(new KafkaTopicConfig(name, defaultOffset, deltaType, key, value)); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapter.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapter.java index 42545a62d1..ec5e92b1e7 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapter.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapter.java @@ -29,6 +29,7 @@ import jakarta.json.bind.adapter.JsonbAdapter; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfigBuilder; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfig; @@ -114,7 +115,7 @@ public JsonObject adaptToJson( public OptionsConfig adaptFromJson( JsonObject object) { - + KafkaOptionsConfigBuilder optionsBuilder = KafkaOptionsConfig.builder(); JsonArray bootstrapArray = object.containsKey(BOOTSTRAP_NAME) ? object.getJsonArray(BOOTSTRAP_NAME) : null; @@ -131,29 +132,23 @@ public OptionsConfig adaptFromJson( ? object.getJsonObject(SASL_NAME) : null; - List bootstrap = null; - if (bootstrapArray != null) { - List bootstrap0 = new ArrayList<>(); - bootstrapArray.forEach(v -> bootstrap0.add(JsonString.class.cast(v).getString())); - bootstrap = bootstrap0; + List bootstrap = new ArrayList<>(); + bootstrapArray.forEach(v -> bootstrap.add(JsonString.class.cast(v).getString())); + optionsBuilder.bootstrap(bootstrap); } - List topics = null; - if (topicsArray != null) { - List topics0 = new ArrayList<>(); - topicsArray.forEach(v -> topics0.add(topic.adaptFromJson(v.asJsonObject()))); - topics = topics0; + List topics = new ArrayList<>(); + topicsArray.forEach(v -> topics.add(topic.adaptFromJson(v.asJsonObject()))); + optionsBuilder.topics(topics); } - List servers = null; - if (serversArray != null) { - List servers0 = new ArrayList<>(); + List servers = new ArrayList<>(); serversArray.forEach(v -> { final String server = JsonString.class.cast(v).getString(); @@ -163,23 +158,25 @@ public OptionsConfig adaptFromJson( final String host = matcher.group(1); final int port = Integer.parseInt(matcher.group(2)); - servers0.add(new KafkaServerConfig(host, port)); + servers.add(KafkaServerConfig.builder().host(host).port(port).build()); } }); - servers = servers0; + optionsBuilder.servers(servers); } - KafkaSaslConfig sasl = null; - if (saslObject != null) { final String mechanism = saslObject.getString(SASL_MECHANISM_NAME); final String username = saslObject.getString(SASL_PLAIN_USERNAME_NAME); final String password = saslObject.getString(SASL_PLAIN_PASSWORD_NAME); - sasl = new KafkaSaslConfig(mechanism, username, password); + optionsBuilder.sasl(KafkaSaslConfig.builder() + .mechanism(mechanism) + .username(username) + .password(password) + .build()); } - return new KafkaOptionsConfig(bootstrap, topics, servers, sasl); + return optionsBuilder.build(); } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicConfigAdapter.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicConfigAdapter.java index 2fb99e5ca0..2e0ff908f1 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicConfigAdapter.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicConfigAdapter.java @@ -21,9 +21,9 @@ import jakarta.json.bind.adapter.JsonbAdapter; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfigBuilder; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaDeltaType; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaOffsetType; -import io.aklivity.zilla.runtime.engine.config.ModelConfig; import io.aklivity.zilla.runtime.engine.config.ModelConfigAdapter; public final class KafkaTopicConfigAdapter implements JsonbAdapter @@ -77,24 +77,24 @@ public JsonObject adaptToJson( public KafkaTopicConfig adaptFromJson( JsonObject object) { + KafkaTopicConfigBuilder topicBuilder = KafkaTopicConfig.builder(); String name = object.containsKey(NAME_NAME) - ? object.getString(NAME_NAME) - : null; + ? object.getString(NAME_NAME) + : null; + topicBuilder.name(name); - KafkaOffsetType defaultOffset = object.containsKey(DEFAULT_OFFSET_NAME) + topicBuilder.defaultOffset(object.containsKey(DEFAULT_OFFSET_NAME) ? KafkaOffsetType.valueOf(object.getString(DEFAULT_OFFSET_NAME).toUpperCase()) - : null; + : null); - KafkaDeltaType deltaType = object.containsKey(DELTA_TYPE_NAME) + topicBuilder.deltaType(object.containsKey(DELTA_TYPE_NAME) ? KafkaDeltaType.valueOf(object.getString(DELTA_TYPE_NAME).toUpperCase()) - : null; + : null); JsonObject key = object.containsKey(EVENT_KEY) ? object.getJsonObject(EVENT_KEY) : null; - ModelConfig keyConfig = null; - if (key != null) { JsonObjectBuilder keyObject = Json.createObjectBuilder(); @@ -102,15 +102,13 @@ public KafkaTopicConfig adaptFromJson( key.forEach(keyObject::add); keyObject.add(SUBJECT, name + "-key"); - keyConfig = converter.adaptFromJson(keyObject.build()); + topicBuilder.key(converter.adaptFromJson(keyObject.build())); } JsonObject value = object.containsKey(EVENT_VALUE) ? object.getJsonObject(EVENT_VALUE) : null; - ModelConfig valueConfig = null; - if (value != null) { JsonObjectBuilder valueObject = Json.createObjectBuilder(); @@ -118,9 +116,9 @@ public KafkaTopicConfig adaptFromJson( value.forEach(valueObject::add); valueObject.add(SUBJECT, name + "-value"); - valueConfig = converter.adaptFromJson(valueObject.build()); + topicBuilder.value(converter.adaptFromJson(valueObject.build())); } - return new KafkaTopicConfig(name, defaultOffset, deltaType, keyConfig, valueConfig); + return topicBuilder.build(); } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java index ba89f747c1..cee60290bd 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java @@ -2440,7 +2440,10 @@ private void onFindCoordinator( delegate.nodeId = String.valueOf(nodeId); - KafkaServerConfig server = new KafkaServerConfig(host.asString(), port); + KafkaServerConfig server = KafkaServerConfig.builder() + .host(host.asString()) + .port(port) + .build(); delegate.client = new DescribeClient(originId, routedId, server, sasl, delegate); delegate.client.doNetworkBegin(traceId, authorization, 0); diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java index 94d2598daf..732dc9f7d7 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java @@ -1786,7 +1786,7 @@ private void onDecodeBroker( String host, int port) { - newServers.put(brokerId, new KafkaServerConfig(host, port)); + newServers.put(brokerId, KafkaServerConfig.builder().host(host).port(port).build()); } private void onDecodeBrokers() diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java index 7fec24693c..65bd5d6686 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java @@ -208,7 +208,10 @@ public MessageConsumer newStream( final KafkaSaslConfig sasl = resolveSasl.apply(binding.sasl()); // TODO: use affinity (like meta, fetch, produce) instead of host and port - final KafkaServerConfig server = new KafkaServerConfig(host, port); + final KafkaServerConfig server = KafkaServerConfig.builder() + .host(host) + .port(port) + .build(); newStream = new KafkaOffsetCommitStream( application, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java index 35f415c6f7..35369efb3d 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java @@ -208,7 +208,10 @@ public MessageConsumer newStream( final KafkaSaslConfig sasl = resolveSasl.apply(binding.sasl()); // TODO: use affinity (like meta, fetch, produce) instead of host and port - final KafkaServerConfig server = new KafkaServerConfig(host, port); + final KafkaServerConfig server = KafkaServerConfig.builder() + .host(host) + .port(port) + .build(); newStream = new KafkaOffsetFetchStream( application, diff --git a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapterTest.java b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapterTest.java index d94b68fa5f..d87d2c7137 100644 --- a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapterTest.java +++ b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapterTest.java @@ -77,7 +77,8 @@ public void shouldReadOptions() assertThat(options, not(nullValue())); assertThat(options.bootstrap, equalTo(singletonList("test"))); - assertThat(options.topics, equalTo(singletonList(new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, null)))); + assertThat(options.topics, equalTo(singletonList(KafkaTopicConfig.builder() + .name("test").defaultOffset(LIVE).deltaType(JSON_PATCH).build()))); assertThat(options.sasl.mechanism, equalTo("plain")); assertThat(options.sasl.username, equalTo("username")); assertThat(options.sasl.password, equalTo("password")); @@ -86,11 +87,24 @@ public void shouldReadOptions() @Test public void shouldWriteOptions() { - KafkaOptionsConfig options = new KafkaOptionsConfig( - singletonList("test"), - singletonList(new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, TestModelConfig.builder().build())), - singletonList(new KafkaServerConfig("localhost", 9092)), - new KafkaSaslConfig("plain", "username", "password")); + KafkaOptionsConfig options = KafkaOptionsConfig.builder() + .bootstrap(singletonList("test")) + .topics( + singletonList(KafkaTopicConfig.builder() + .name("test") + .defaultOffset(LIVE) + .deltaType(JSON_PATCH) + .value(TestModelConfig.builder().build()) + .build())) + .servers(singletonList(KafkaServerConfig.builder() + .host("localhost") + .port(9092).build())) + .sasl(KafkaSaslConfig.builder() + .mechanism("plain") + .username("username") + .password("password") + .build()) + .build(); String text = jsonb.toJson(options); @@ -132,7 +146,7 @@ public void shouldReadSaslScramOptions() assertThat(options, not(nullValue())); assertThat(options.bootstrap, equalTo(singletonList("test"))); assertThat(options.topics, equalTo(singletonList( - new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, null)))); + KafkaTopicConfig.builder().name("test").defaultOffset(LIVE).deltaType(JSON_PATCH).build()))); assertThat(options.sasl.mechanism, equalTo("scram-sha-256")); assertThat(options.sasl.username, equalTo("username")); assertThat(options.sasl.password, equalTo("password")); @@ -141,11 +155,23 @@ public void shouldReadSaslScramOptions() @Test public void shouldWriteSaslScramOptions() { - KafkaOptionsConfig options = new KafkaOptionsConfig( - singletonList("test"), - singletonList(new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, null)), - singletonList(new KafkaServerConfig("localhost", 9092)), - new KafkaSaslConfig("scram-sha-256", "username", "password")); + KafkaOptionsConfig options = KafkaOptionsConfig.builder() + .bootstrap(singletonList("test")) + .topics(singletonList(KafkaTopicConfig.builder() + .name("test") + .defaultOffset(LIVE) + .deltaType(JSON_PATCH) + .build())) + .servers(singletonList(KafkaServerConfig.builder() + .host("localhost") + .port(9092) + .build())) + .sasl(KafkaSaslConfig.builder() + .mechanism("scram-sha-256") + .username("username") + .password("password") + .build()) + .build(); String text = jsonb.toJson(options); @@ -159,14 +185,24 @@ public void shouldWriteSaslScramOptions() @Test public void shouldWriteCatalogOptions() { - KafkaOptionsConfig options = new KafkaOptionsConfig( - singletonList("test"), - singletonList(new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, - TestModelConfig.builder() - .length(0) - .build())), - singletonList(new KafkaServerConfig("localhost", 9092)), - new KafkaSaslConfig("plain", "username", "password")); + KafkaOptionsConfig options = KafkaOptionsConfig.builder() + .bootstrap(singletonList("test")) + .topics(singletonList(KafkaTopicConfig.builder() + .name("test") + .defaultOffset(LIVE) + .deltaType(JSON_PATCH) + .value(TestModelConfig.builder().length(0).build()) + .build())) + .servers(singletonList(KafkaServerConfig.builder() + .host("localhost") + .port(9092) + .build())) + .sasl(KafkaSaslConfig.builder() + .mechanism("plain") + .username("username") + .password("password") + .build()) + .build(); String text = jsonb.toJson(options); diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json index 53f7883936..ab50bee880 100644 --- a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json @@ -101,36 +101,7 @@ "pattern": "([^\\:]+):(\\d+)" } }, - "sasl": - { - "title": "SASL", - "type": "object", - "properties": - { - "mechanism": - { - "title": "Mechanism", - "type": "string", - "enum": [ "plain", "scram-sha-1", "scram-sha-256", "scram-sha-512" ] - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "mechanism", - "username", - "password" - ] - } + "sasl": "$defs/options/binding/kafka/sasl" }, "additionalProperties": false }, @@ -183,5 +154,129 @@ ] } } + }, + { + "op": "add", + "path": "/$defs/options/binding/kafka", + "value": + { + "sasl": + { + "title": "SASL", + "type": "object", + "properties": + { + "mechanism": + { + "title": "Mechanism", + "type": "string", + "enum": [ "plain", "scram-sha-1", "scram-sha-256", "scram-sha-512" ] + } + }, + "oneOf": + [ + { + "additionalProperties": false, + "properties": + { + "mechanism": + { + "const": "plain" + }, + "username": + { + "title": "Username", + "type": "string" + }, + "password": + { + "title": "Password", + "type": "string" + } + }, + "required": + [ + "username", + "password" + ] + }, + { + "additionalProperties": false, + "properties": + { + "mechanism": + { + "const": "scram-sha-1" + }, + "username": + { + "title": "Username", + "type": "string" + }, + "password": + { + "title": "Password", + "type": "string" + } + }, + "required": + [ + "username", + "password" + ] + }, + { + "additionalProperties": false, + "properties": + { + "mechanism": + { + "const": "scram-sha-256" + }, + "username": + { + "title": "Username", + "type": "string" + }, + "password": + { + "title": "Password", + "type": "string" + } + }, + "required": + [ + "username", + "password" + ] + }, + { + "additionalProperties": false, + "properties": + { + "mechanism": + { + "const": "scram-sha-512" + }, + "username": + { + "title": "Username", + "type": "string" + }, + "password": + { + "title": "Password", + "type": "string" + } + }, + "required": + [ + "username", + "password" + ] + } + ] + } + } } ] From 4cf449390a503cd09d871dd2934da4d434f7391c Mon Sep 17 00:00:00 2001 From: AJ Danelz Date: Tue, 27 Feb 2024 00:51:23 -0500 Subject: [PATCH 13/25] feat: use env var to add incubator java option (#811) use the variable test operator to save line length --- .../zilla/manager/internal/commands/install/ZpmInstall.java | 1 + 1 file changed, 1 insertion(+) diff --git a/manager/src/main/java/io/aklivity/zilla/manager/internal/commands/install/ZpmInstall.java b/manager/src/main/java/io/aklivity/zilla/manager/internal/commands/install/ZpmInstall.java index fcba561d66..9278bfc1d7 100644 --- a/manager/src/main/java/io/aklivity/zilla/manager/internal/commands/install/ZpmInstall.java +++ b/manager/src/main/java/io/aklivity/zilla/manager/internal/commands/install/ZpmInstall.java @@ -704,6 +704,7 @@ private void generateLauncher() throws IOException Path zillaPath = launcherDir.resolve("zilla"); Files.write(zillaPath, Arrays.asList( "#!/bin/sh", + "JAVA_OPTIONS+=\"${ZILLA_INCUBATOR_ENABLED:+ -Dzilla.incubator.enabled=$ZILLA_INCUBATOR_ENABLED}\"", "cd \"${0%/*}\"", String.format(String.join(" ", Arrays.asList( "exec %s/bin/java", From 9a81c7f332ab0a758acf960d7e4f6d3ff36734aa Mon Sep 17 00:00:00 2001 From: bmaidics Date: Tue, 27 Feb 2024 22:12:55 +0100 Subject: [PATCH 14/25] Fix kafka client composite resolvedId (#816) --- .../asyncapi/internal/config/AsyncapiBindingConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java index c85f74fad3..02564005b2 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java @@ -14,6 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CACHE_CLIENT; import static java.util.Collections.unmodifiableMap; import static java.util.stream.Collector.of; import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; @@ -69,7 +70,7 @@ public AsyncapiBindingConfig( this.resolvedIds = binding.composites.stream() .map(c -> c.bindings) .flatMap(List::stream) - .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka")) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka") && b.kind == CACHE_CLIENT) .collect(of( () -> new Long2LongHashMap(-1), (m, r) -> m.put(0L, r.id), //TODO: populate proper apiId @@ -80,7 +81,7 @@ public AsyncapiBindingConfig( binding.composites.stream() .map(c -> c.bindings) .flatMap(List::stream) - .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka")) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka") && b.kind == CACHE_CLIENT) .forEach(b -> this.composites.put(NamespacedId.namespaceId(b.id), b.type)); this.paths = new Object2ObjectHashMap<>(); From d0a8f21e5bcc2ac008554031818539b331a3f6e6 Mon Sep 17 00:00:00 2001 From: John Fallows Date: Tue, 27 Feb 2024 18:04:48 -0800 Subject: [PATCH 15/25] Update openapi binding module-info to read org.leadpony.justify --- incubator/binding-openapi/src/main/moditect/module-info.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/incubator/binding-openapi/src/main/moditect/module-info.java b/incubator/binding-openapi/src/main/moditect/module-info.java index 3a3f23238d..483f8145bd 100644 --- a/incubator/binding-openapi/src/main/moditect/module-info.java +++ b/incubator/binding-openapi/src/main/moditect/module-info.java @@ -14,6 +14,8 @@ */ module io.aklivity.zilla.runtime.binding.openapi { + requires org.leadpony.justify; + requires io.aklivity.zilla.runtime.engine; requires io.aklivity.zilla.runtime.binding.http; requires io.aklivity.zilla.runtime.binding.tcp; From 77be54a01ce935c8975134fc4951d7e268197159 Mon Sep 17 00:00:00 2001 From: Attila Kreiner Date: Wed, 28 Feb 2024 20:34:28 +0100 Subject: [PATCH 16/25] Support local logging of events (#755) --- cloud/docker-image/pom.xml | 6 + .../src/main/docker/zpm.json.template | 3 +- .../catalog-schema-registry.spec/pom.xml | 20 ++ .../META-INF/zilla/schema_registry.idl | 36 ++ incubator/catalog-schema-registry/pom.xml | 12 +- .../internal/SchemaRegistryCatalog.java | 2 +- .../SchemaRegistryCatalogContext.java | 11 +- .../SchemaRegistryCatalogHandler.java | 21 +- .../internal/SchemaRegistryEventContext.java | 64 ++++ .../registry/internal/SchemaRegistryIT.java | 21 +- incubator/exporter-stdout.spec/COPYRIGHT | 12 + incubator/exporter-stdout.spec/LICENSE | 114 ++++++ incubator/exporter-stdout.spec/NOTICE | 18 + .../exporter-stdout.spec/NOTICE.template | 13 + incubator/exporter-stdout.spec/mvnw | 310 ++++++++++++++++ incubator/exporter-stdout.spec/mvnw.cmd | 182 ++++++++++ incubator/exporter-stdout.spec/pom.xml | 180 ++++++++++ .../src/main/moditect/module-info.java | 18 + .../server.authorization.credentials.yaml | 53 +++ .../binding/http/config/v1.1/server.yaml | 33 ++ .../specs/binding/http/config/v2/server.yaml | 33 ++ .../specs/binding/kafka/config/client.yaml | 26 ++ .../specs/binding/tcp/config/client.host.yaml | 28 ++ .../specs/binding/tls/config/server.yaml | 38 ++ .../binding/tls/config/stores/.gitignore | 2 + .../binding/tls/config/stores/client/keys | Bin 0 -> 3593 bytes .../binding/tls/config/stores/client/signers | Bin 0 -> 2579 bytes .../binding/tls/config/stores/client/trust | Bin 0 -> 1178 bytes .../binding/tls/config/stores/server/keys | Bin 0 -> 3701 bytes .../binding/tls/config/stores/server/signers | Bin 0 -> 2579 bytes .../binding/tls/config/stores/server/trust | Bin 0 -> 1178 bytes .../stdout/schema/stdout.schema.patch.json | 34 ++ incubator/exporter-stdout/COPYRIGHT | 12 + incubator/exporter-stdout/LICENSE | 114 ++++++ incubator/exporter-stdout/NOTICE | 13 + incubator/exporter-stdout/NOTICE.template | 13 + incubator/exporter-stdout/mvnw | 310 ++++++++++++++++ incubator/exporter-stdout/mvnw.cmd | 182 ++++++++++ incubator/exporter-stdout/pom.xml | 340 ++++++++++++++++++ .../stdout/internal/StdoutConfiguration.java | 67 ++++ .../stdout/internal/StdoutExporter.java | 53 +++ .../internal/StdoutExporterContext.java | 76 ++++ .../internal/StdoutExporterFactorySpi.java | 35 ++ .../internal/StdoutExporterHandler.java | 57 +++ .../internal/config/StdoutExporterConfig.java | 33 ++ .../internal/config/StdoutOptionsConfig.java | 21 ++ .../config/StdoutOptionsConfigAdapter.java | 56 +++ .../stdout/internal/stream/EventHandler.java | 70 ++++ .../internal/stream/StdoutEventsStream.java | 65 ++++ .../internal/stream/StdoutHttpHandler.java | 57 +++ .../internal/stream/StdoutJwtHandler.java | 54 +++ .../internal/stream/StdoutKafkaHandler.java | 65 ++++ .../stream/StdoutSchemaRegistryHandler.java | 55 +++ .../internal/stream/StdoutTcpHandler.java | 54 +++ .../internal/stream/StdoutTlsHandler.java | 88 +++++ .../src/main/moditect/module-info.java | 24 ++ ...time.engine.config.OptionsConfigAdapterSpi | 1 + ...runtime.engine.exporter.ExporterFactorySpi | 1 + .../internal/StdoutExporterFactoryTest.java | 51 +++ .../StdoutOptionsConfigAdapterTest.java | 68 ++++ .../internal/events/Http11EventsIT.java | 68 ++++ .../stdout/internal/events/Http2EventsIT.java | 68 ++++ .../stdout/internal/events/JwtEventsIT.java | 68 ++++ .../stdout/internal/events/KafkaEventsIT.java | 73 ++++ .../internal/events/StdoutOutputRule.java | 65 ++++ .../stdout/internal/events/TcpEventsIT.java | 75 ++++ .../stdout/internal/events/TlsEventsIT.java | 80 +++++ incubator/pom.xml | 7 + .../http/internal/HttpEventContext.java | 102 ++++++ .../internal/stream/HttpServerFactory.java | 16 +- .../kafka/internal/KafkaEventContext.java | 80 +++++ .../stream/KafkaClientDescribeFactory.java | 10 + .../stream/KafkaClientFetchFactory.java | 13 + .../stream/KafkaClientGroupFactory.java | 18 + .../stream/KafkaClientMetaFactory.java | 13 + .../KafkaClientOffsetCommitFactory.java | 2 + .../stream/KafkaClientOffsetFetchFactory.java | 2 + .../stream/KafkaClientProduceFactory.java | 12 + .../stream/KafkaClientSaslHandshaker.java | 25 ++ .../internal/stream/MqttServerFactory.java | 2 +- runtime/binding-tcp/pom.xml | 4 +- .../binding/tcp/internal/TcpEventContext.java | 62 ++++ .../tcp/internal/stream/TcpClientFactory.java | 8 +- .../tcp/internal/stream/TcpClientRouter.java | 149 +++++--- .../tcp/internal/streams/ClientIT.java | 21 ++ runtime/binding-tls/pom.xml | 4 +- .../binding/tls/internal/TlsEventContext.java | 120 +++++++ .../tls/internal/stream/TlsClientFactory.java | 297 ++++++++++----- .../tls/internal/stream/TlsServerFactory.java | 326 +++++++++++------ .../aklivity/zilla/runtime/engine/Engine.java | 52 ++- .../runtime/engine/EngineConfiguration.java | 14 + .../zilla/runtime/engine/EngineContext.java | 11 + .../binding/function/MessageReader.java | 23 ++ .../runtime/engine/guard/GuardHandler.java | 2 + .../engine/internal/layouts/EventsLayout.java | 173 +++++++++ .../internal/registry/EngineWorker.java | 51 +++ .../internal/spy/OneToOneRingBufferSpy.java | 167 +++++++++ .../engine/internal/spy/RingBufferSpy.java | 41 +++ .../runtime/engine/internal/EngineTest.java | 25 ++ .../internal/layouts/EventsLayoutTest.java | 62 ++++ .../metrics/HistogramsLayoutTest.java | 4 +- .../metrics/ScalarsLayoutTest.java | 4 +- .../test/internal/guard/TestGuardHandler.java | 2 + runtime/guard-jwt/pom.xml | 19 + .../guard/jwt/internal/JwtEventContext.java | 61 ++++ .../guard/jwt/internal/JwtGuardContext.java | 4 +- .../guard/jwt/internal/JwtGuardHandler.java | 14 +- .../jwt/internal/JwtGuardHandlerTest.java | 104 +++--- .../guard/jwt/internal/JwtGuardTest.java | 14 +- .../main/resources/META-INF/zilla/http.idl | 22 ++ .../reject.credentials.header/client.rpt | 2 +- .../reject.credentials.header/server.rpt | 2 +- .../main/resources/META-INF/zilla/kafka.idl | 21 ++ .../main/resources/META-INF/zilla/mqtt.idl | 1 - specs/binding-tcp.spec/pom.xml | 16 + .../src/main/resources/META-INF/zilla/tcp.idl | 35 ++ specs/binding-tls.spec/pom.xml | 16 + .../src/main/resources/META-INF/zilla/tls.idl | 38 ++ .../main/resources/META-INF/zilla/core.idl | 10 + specs/guard-jwt.spec/pom.xml | 20 ++ .../src/main/resources/META-INF/zilla/jwt.idl | 34 ++ 121 files changed, 5832 insertions(+), 347 deletions(-) create mode 100644 incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl create mode 100644 incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java create mode 100644 incubator/exporter-stdout.spec/COPYRIGHT create mode 100644 incubator/exporter-stdout.spec/LICENSE create mode 100644 incubator/exporter-stdout.spec/NOTICE create mode 100644 incubator/exporter-stdout.spec/NOTICE.template create mode 100755 incubator/exporter-stdout.spec/mvnw create mode 100644 incubator/exporter-stdout.spec/mvnw.cmd create mode 100644 incubator/exporter-stdout.spec/pom.xml create mode 100644 incubator/exporter-stdout.spec/src/main/moditect/module-info.java create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust create mode 100644 incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json create mode 100644 incubator/exporter-stdout/COPYRIGHT create mode 100644 incubator/exporter-stdout/LICENSE create mode 100644 incubator/exporter-stdout/NOTICE create mode 100644 incubator/exporter-stdout/NOTICE.template create mode 100755 incubator/exporter-stdout/mvnw create mode 100644 incubator/exporter-stdout/mvnw.cmd create mode 100644 incubator/exporter-stdout/pom.xml create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java create mode 100644 incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java create mode 100644 incubator/exporter-stdout/src/main/moditect/module-info.java create mode 100644 incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi create mode 100644 incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java create mode 100644 incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java create mode 100644 runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java create mode 100644 runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java create mode 100644 runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java create mode 100644 runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java create mode 100644 runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java create mode 100644 runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java create mode 100644 runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java create mode 100644 runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java create mode 100644 runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java rename runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/{layout => layouts}/metrics/HistogramsLayoutTest.java (98%) rename runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/{layout => layouts}/metrics/ScalarsLayoutTest.java (96%) create mode 100644 runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java create mode 100644 specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl create mode 100644 specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl create mode 100644 specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index a02ca90ede..d70ae69cac 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -235,6 +235,12 @@ ${project.version} runtime + + ${project.groupId} + exporter-stdout + ${project.version} + runtime + ${project.groupId} metrics-stream diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template index db0233de50..94335ae43d 100644 --- a/cloud/docker-image/src/main/docker/zpm.json.template +++ b/cloud/docker-image/src/main/docker/zpm.json.template @@ -45,8 +45,9 @@ "io.aklivity.zilla:command-stop", "io.aklivity.zilla:command-tune", "io.aklivity.zilla:engine", - "io.aklivity.zilla:exporter-prometheus", "io.aklivity.zilla:exporter-otlp", + "io.aklivity.zilla:exporter-prometheus", + "io.aklivity.zilla:exporter-stdout", "io.aklivity.zilla:guard-jwt", "io.aklivity.zilla:metrics-stream", "io.aklivity.zilla:metrics-http", diff --git a/incubator/catalog-schema-registry.spec/pom.xml b/incubator/catalog-schema-registry.spec/pom.xml index 6e529c8f30..85398279c4 100644 --- a/incubator/catalog-schema-registry.spec/pom.xml +++ b/incubator/catalog-schema-registry.spec/pom.xml @@ -63,6 +63,23 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core schema_registry + io.aklivity.zilla.specs.catalog.schema.registry.internal.types + + + + + validate + generate + + + + com.mycila license-maven-plugin @@ -86,6 +103,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/specs/catalog/schema/registry/internal/types/**/*.class + BUNDLE diff --git a/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl b/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl new file mode 100644 index 0000000000..df61e3605c --- /dev/null +++ b/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +scope schema_registry +{ + scope event + { + enum SchemaRegistryEventType (uint8) + { + REMOTE_ACCESS_REJECTED (1) + } + + struct SchemaRegistryRemoteAccessRejected extends core::event::Event + { + string8 method; + string16 url; + int16 status; + } + + union SchemaRegistryEvent switch (SchemaRegistryEventType) + { + case REMOTE_ACCESS_REJECTED: SchemaRegistryRemoteAccessRejected remoteAccessRejected; + } + } +} diff --git a/incubator/catalog-schema-registry/pom.xml b/incubator/catalog-schema-registry/pom.xml index 74a7b83a1a..22c39b3d02 100644 --- a/incubator/catalog-schema-registry/pom.xml +++ b/incubator/catalog-schema-registry/pom.xml @@ -24,7 +24,7 @@ 11 11 - 0.90 + 0.80 0 @@ -71,16 +71,12 @@ org.jasig.maven maven-notice-plugin - - com.mycila - license-maven-plugin - ${project.groupId} flyweight-maven-plugin ${project.version} - internal + core schema_registry internal io.aklivity.zilla.runtime.catalog.schema.registry.internal.types @@ -91,6 +87,10 @@ + + com.mycila + license-maven-plugin + maven-checkstyle-plugin diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java index 8affce6dc5..c7ebe83402 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java @@ -40,7 +40,7 @@ public String name() public CatalogContext supply( EngineContext context) { - return new SchemaRegistryCatalogContext(); + return new SchemaRegistryCatalogContext(context); } @Override diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java index cf5c8eb7b6..1dbfd3c22f 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java @@ -15,16 +15,25 @@ package io.aklivity.zilla.runtime.catalog.schema.registry.internal; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.config.CatalogConfig; public class SchemaRegistryCatalogContext implements CatalogContext { + private final EngineContext context; + + public SchemaRegistryCatalogContext( + EngineContext context) + { + this.context = context; + } + @Override public CatalogHandler attach( CatalogConfig catalog) { - return new SchemaRegistryCatalogHandler(SchemaRegistryOptionsConfig.class.cast(catalog.options)); + return new SchemaRegistryCatalogHandler(SchemaRegistryOptionsConfig.class.cast(catalog.options), context, catalog.id); } } diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java index c8bc750709..88f47fd777 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java @@ -30,6 +30,7 @@ import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.serializer.RegisterSchemaRequest; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.types.SchemaRegistryPrefixFW; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.model.function.ValueConsumer; @@ -51,9 +52,13 @@ public class SchemaRegistryCatalogHandler implements CatalogHandler private final Int2ObjectCache schemas; private final Int2ObjectCache schemaIds; private final long maxAgeMillis; + private final SchemaRegistryEventContext event; + private final long catalogId; public SchemaRegistryCatalogHandler( - SchemaRegistryOptionsConfig config) + SchemaRegistryOptionsConfig config, + EngineContext context, + long catalogId) { this.baseUrl = config.url; this.client = HttpClient.newHttpClient(); @@ -62,6 +67,8 @@ public SchemaRegistryCatalogHandler( this.schemas = new Int2ObjectCache<>(1, 1024, i -> {}); this.schemaIds = new Int2ObjectCache<>(1, 1024, i -> {}); this.maxAgeMillis = config.maxAge.toMillis(); + this.event = new SchemaRegistryEventContext(context); + this.catalogId = catalogId; } @Override @@ -209,12 +216,18 @@ private String sendHttpRequest( try { - HttpResponse response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); - return response.statusCode() == 200 ? response.body() : null; + HttpResponse httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + boolean success = httpResponse.statusCode() == 200; + String responseBody = success ? httpResponse.body() : null; + if (!success) + { + event.remoteAccessRejected(catalogId, httpRequest, httpResponse.statusCode()); + } + return responseBody; } catch (Exception ex) { - ex.printStackTrace(System.out); + event.remoteAccessRejected(catalogId, httpRequest, 0); } return null; } diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java new file mode 100644 index 0000000000..42770a31b1 --- /dev/null +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.catalog.schema.registry.internal; + +import java.net.http.HttpRequest; +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.catalog.schema.registry.internal.types.event.SchemaRegistryEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class SchemaRegistryEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final SchemaRegistryEventFW.Builder schemaRegistryEventRW = new SchemaRegistryEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int schemaRegistryTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public SchemaRegistryEventContext( + EngineContext context) + { + this.schemaRegistryTypeId = context.supplyTypeId(SchemaRegistryCatalog.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void remoteAccessRejected( + long catalogId, + HttpRequest httpRequest, + int status) + { + SchemaRegistryEventFW event = schemaRegistryEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .remoteAccessRejected(e -> e + .timestamp(clock.millis()) + .traceId(0L) + .namespacedId(catalogId) + .method(httpRequest.method()) + .url(httpRequest.uri().toString()) + .status((short) status) + ) + .build(); + eventWriter.accept(schemaRegistryTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java b/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java index f65af539c1..f04ed60d0b 100644 --- a/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java +++ b/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.rules.RuleChain.outerRule; +import static org.mockito.Mockito.mock; import java.time.Duration; @@ -35,6 +36,7 @@ import org.kaazing.k3po.junit.rules.K3poRule; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.model.function.ValueConsumer; @@ -49,6 +51,7 @@ public class SchemaRegistryIT public final TestRule chain = outerRule(k3po).around(timeout); private SchemaRegistryOptionsConfig config; + private EngineContext context = mock(EngineContext.class); @Before public void setup() @@ -69,7 +72,7 @@ public void shouldResolveSchemaViaSchemaId() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); String schema = catalog.resolve(9); @@ -88,7 +91,7 @@ public void shouldResolveSchemaViaSubjectVersion() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); int schemaId = catalog.resolve("items-snapshots-value", "latest"); @@ -109,7 +112,7 @@ public void shouldRegisterSchema() throws Exception String schema = "{\"type\": \"record\",\"name\": \"test\",\"fields\":[{\"type\": \"string\",\"name\": \"field1\"}," + "{\"type\": \"com.acme.Referenced\",\"name\": \"int\"}]}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); int schemaId = catalog.register("items-snapshots-value", "avro", schema); @@ -128,7 +131,7 @@ public void shouldResolveSchemaViaSchemaIdFromCache() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); catalog.resolve(9); @@ -149,7 +152,7 @@ public void shouldResolveSchemaViaSubjectVersionFromCache() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); catalog.resolve(catalog.resolve("items-snapshots-value", "latest")); @@ -167,7 +170,7 @@ public void shouldResolveSchemaViaSubjectVersionFromCache() throws Exception @Test public void shouldVerifyMaxPadding() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); assertEquals(5, catalog.encodePadding()); } @@ -175,7 +178,7 @@ public void shouldVerifyMaxPadding() @Test public void shouldVerifyEncodedData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); @@ -191,7 +194,7 @@ public void shouldVerifyEncodedData() public void shouldResolveSchemaIdAndProcessData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); @@ -207,7 +210,7 @@ public void shouldResolveSchemaIdAndProcessData() @Test public void shouldResolveSchemaIdFromData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); diff --git a/incubator/exporter-stdout.spec/COPYRIGHT b/incubator/exporter-stdout.spec/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/exporter-stdout.spec/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/exporter-stdout.spec/LICENSE b/incubator/exporter-stdout.spec/LICENSE new file mode 100644 index 0000000000..f3cf11c3ad --- /dev/null +++ b/incubator/exporter-stdout.spec/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/exporter-stdout.spec/NOTICE b/incubator/exporter-stdout.spec/NOTICE new file mode 100644 index 0000000000..cf99769e41 --- /dev/null +++ b/incubator/exporter-stdout.spec/NOTICE @@ -0,0 +1,18 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + diff --git a/incubator/exporter-stdout.spec/NOTICE.template b/incubator/exporter-stdout.spec/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/exporter-stdout.spec/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/exporter-stdout.spec/mvnw b/incubator/exporter-stdout.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/exporter-stdout.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/exporter-stdout.spec/mvnw.cmd b/incubator/exporter-stdout.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/exporter-stdout.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/exporter-stdout.spec/pom.xml b/incubator/exporter-stdout.spec/pom.xml new file mode 100644 index 0000000000..1b07db88bf --- /dev/null +++ b/incubator/exporter-stdout.spec/pom.xml @@ -0,0 +1,180 @@ + + + + 4.0.0 + + io.aklivity.zilla + specs + develop-SNAPSHOT + ../pom.xml + + + exporter-stdout.spec + zilla::incubator::exporter-stdout.spec + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 1.00 + 0 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + test + + + ${project.groupId} + binding-proxy.spec + ${project.version} + test + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core + io.aklivity.zilla.specs.exporter.stdout.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + **/keys + **/trust + **/signers + + + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/exporter/stdout/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/incubator/exporter-stdout.spec/src/main/moditect/module-info.java b/incubator/exporter-stdout.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..9b8473aaae --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/moditect/module-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +open module io.aklivity.zilla.specs.exporter.stdout +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml new file mode 100644 index 0000000000..a6daa035f9 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml @@ -0,0 +1,53 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +guards: + jwt0: + type: jwt + options: + issuer: https://auth.example.com + audience: https://api.example.com + keys: + - kty: RSA + n: qqEu50hX+43Bx4W1UYWnAVKwFm+vDbP0kuIOSLVNa+HKQdHTf+3Sei5UCnkskn796izA29D0DdCy3ET9oaKRHIJyKbqFl0rv6f516QzOoXKC6N01sXBHBE/ovs0wwDvlaW+gFGPgkzdcfUlyrWLDnLV7LcuQymhTND2uH0oR3wJnNENN/OFgM1KGPPDOe19YsIKdLqARgxrhZVsh06OurEviZTXOBFI5r+yac7haDwOQhLHXNv+Y9MNvxs5QLWPFIM3bNUWfYrJnLrs4hGJS+y/KDM9Si+HL30QAFXy4YNO33J8DHjZ7ddG5n8/FqplOKvRtUgjcKWlxoGY4VdVaDQ== + e: AQAB + alg: RS256 + kid: example +bindings: + net0: + type: http + kind: server + options: + versions: + - http/1.1 + authorization: + jwt0: + credentials: + cookies: + access_token: "{credentials}" + headers: + authorization: Bearer {credentials} + query: + access_token: "{credentials}" + routes: + - exit: app0 + guarded: + jwt0: [] diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml new file mode 100644 index 0000000000..7202d45307 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + net0: + type: http + kind: server + options: + versions: + - http/1.1 + routes: + - exit: app0 + when: + - headers: + :authority: localhost:8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml new file mode 100644 index 0000000000..effa3e4a6f --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + net0: + type: http + kind: server + options: + versions: + - h2 + routes: + - exit: app0 + when: + - headers: + :authority: localhost:8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml new file mode 100644 index 0000000000..ff4c4a3aea --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + app0: + type: kafka + kind: client + exit: net0 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml new file mode 100644 index 0000000000..04191ddc52 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml @@ -0,0 +1,28 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + app0: + type: tcp + kind: client + options: + host: localhost + port: 8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml new file mode 100644 index 0000000000..fd7e4b90a0 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml @@ -0,0 +1,38 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +vaults: + server: + type: filesystem + options: + keys: + store: stores/server/keys + type: pkcs12 + password: generated +bindings: + net0: + type: tls + kind: server + vault: server + options: + keys: + - localhost + exit: app0 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore new file mode 100644 index 0000000000..507484f3e9 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore @@ -0,0 +1,2 @@ +*.crt +*.csr diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys new file mode 100644 index 0000000000000000000000000000000000000000..d2fa3da9b94ead2e31bc206573eee4c36ae4e95b GIT binary patch literal 3593 zcmY+EbyO1$*T)Bp8Zdgq0O^t#-Q6Y9A)$1aLt0=6=qAVz5b;M!ry??2#QuBQo#h>gbNBhV!S*AMGX)^QSld- zzz~Gq{}mD10TG1Of3f-BdPqd}e_P}rJp4Qa@CJqeUcdy1K>v^b9)|+ymH|)fk#@&{ zpIF_%=UjaAVUcs0_ymBihd>0dpM{p`g_gD5;6WyO9bj7Vwf!Pfx4w%pDxkj=n;`x; zDDArDlB&~#<~^Xn1Gz8omT$(xPmy~zUXt#G&ayQR7Sa+ovumC`Ng;0 z>m#OpsS_h52t~t1Jl6%@#7C-NnKs6m&t0i9m(K!N>WPzBKtsHhq?T%1Mso2Zd*7#+ z=l$9lJ|={3TI0iWcRXdjWLDz5){(Cbdn<3zTA2!{r?$2r6h*DL>4owX%i@p$c6PYn zo+j5II!cG=`})ig1Laqh?((s1gF>AsN^v*N+uER5bJQE{7B5muqv${CqOKqk4!g3` z-LHQvYP=8SmTO zXG`P+bOEA!=0b!qXbcL6Y{HeBQ-$WYzJ2q{48l*rVJ&+Y!6QR=lrRw2VKLvjO7_@t_suSyHqj(ZMvFKzl&Vj66 z3DaCPA=sa8c#zGTQgY~A8Sre8g6Kta1m7!(fExf^RK*k328-^b{ijWwzXoFsOl4L^ zhj#^-?vaFLZaF=368cUwrdqCW84)nUR_cD4mcqt!exRSsonW;atKtFtz`PajA{r_Z z*;TH1@P0@Gp5V+uo86Tj*m%Ass=BT3gey3;U2)hsBAUfwci5>VewC?uhgXITq9aKj zd_onOFhKkZ{Eaakdo=K!r3YK93gIDVNY*Etwd1nZl7;TwUaB?#zoZIJ3^xupu2@dm zN?Mj1sw^7&57*r4wAneYF5;>x>dIpw%!!V}7|pT7>HKz0gS^aKS~PH~Xr%U;IVScd z)DtC(*iFYAqh!+0Y~S$UGBR;uzCSka+?!M1ovB0iRmCzc&!ZOvhFgpi_3iR7W69j3 zRGvl)MfA2X97FppRNU5wS(@`KadINH!Mm$(pt60e#m!8t=e_;au8HHDrl9%*QD&ll z+G*zLa*hEGC(-U*k=<4SG|*<|yKm>>`>|Z5B?X5luAjCqV4Z<@hp*?;eIWJ|JqvMN52bxYEa%*#rd!h}t6xV{30jGvn5hq?0E@KKp^?do` z7M0QuYlN|E%3~R*ES|L>I6z2lVL2l+dz$h>_C}pjX^KRO!@$c*7H z>a-vb;_PTq!sLEo|8%N7GU8y?`sE#g9`YCGC_H3buH3Lr;!ncb)=Q4J>WN|L_HhGq zC(E9ON@IntOV6xrC#TSNT>ZGAtvY%md#Zka{xY86*B>{YR5g<4E}SPwhjes7#nfS} z|5ha_ofs7x7~l->0eAvj0e%2DKn%w5pO{{Z4oG9}>FdfaCM_W+DJd={EhZ@@2SboZ z{(Fd+C=Wrx^A~a8;{pCQ=Kmyk|K(Vs|8eYd4!VZ~HG^E>I={ks_RA;v;W#99O;we6m)q_iU+IihSP~c(K2z5AyYG6weyyJfnpi%>$AGSVYgfKaw2mBtA6*tb%i7zn`m2y8haD3$ipU$?YpWerPk(GUoM5l z++OxBtVf;X>U)O+2s54QV%?> zo}9WTDcCJSd@s9YN_+nbT#26iErnKZ7f>=!4@CcU4_Dp>Gk(YO>_=WYH z3{RzH7$QE!AR#>Y@iR!G4Z=p_ddETtw5pu*2+?OnV=M)}Qn=gUmGq~k1 zb1D|0bV!`2dxwWch~PkwibrG$Wh2(+K4dh@{z4??-qo;LWrFHIkAa8+Y zUQ(^npzM&RQF&P03q~1Prz+fLs);!BYfn3~TsE@4**yt6L)M`gIS4ZzEwuw5%j|wf zuup8y^#E0Cz_OkeT0YN-hW_~SOTOz(L4vKu1wc$a7eb`;^5#rzo*YlZYk8UeUi3|e#RAd-HVSen+RGn(fl?1&8u9aILbSOXlDY&mKwM{;dL?$WrdTy z$9VQzKDs>6Hrd8}v>;M-of7|}AuW}|-YMKi>wk8W)Md*kKan1qWyFzJW}DmnW4aDH z-7RdoI}~Y(JsV^kdZqY!>yPi;arj{57d~V~Pl9d)cJ+Jyg zTGc2UJ>xmIuVC*mwtQ&{{I#l{>$j10edbqEW+zlpoJopGdur06c0Sw~Q5Un3mgU0a zG7?ufRpmC2H;pw;i&knH!}8Qa{lntXH-9yh4Bu@LbertwP~c0dxcwB}p>`t4VrMt> zK`D8%kU?=qW2_jP=^Uq@B=`Krt_Tny?3nBK@ZBFFt(HD8JDa1)L<6Wu;F8;Ypf_~^`&!G$^ym+oBK#n&Y@Kp8=~qP_ z)tbr`^(LN7s2-9|E7_+fr*ZTYLiO!_$B~!eqdvK_$$FisW+^r9sf_JVK?xaSjPFBY za%J;c`Nz9@3Swb`W$bu3FifycCsV(7e9GO$Ix3uptX+oCs^T8jkW-G+42+!^Kat|_ zr*2;M?uN>Hr4PDNreM?CA(~%wL~PR=7qKA^VUHTYW5Qbv0N&)00Or@bQGp>-x|e796N89v8+qn^uy; z;(2eB^j86N6I{`I+`CMHsLR2EIsak)u|p~V;B`EZ zaygQRpB)>$k9NNoC^C2$;1snUV0 zIpps}j*^D+WpI{B!eY=hU%3<}hD9HppnaB>pCn%P$!X)MICXk3-=7dYl;BJ#esrrV z|+?)n+|bAYdeaL1miT|;l}hP+hVACmX>h->+knli?mW@xP5 zP9qfg}bIEhcRT-5Ai_90kw)6Kq)VWt7pX zZ~L+xDcL5zTN}T+gWu__Aotbc;NWh6@tBBTLylg$6N{d-WERW}rVQhNkrEN!2NU2k z69RyAxo#NY44TKn{pVW!C=P*(JBw@+AYC40m}Xxg=ly2+oba+{u3Hwadjp>UFCp(= DFc+_; literal 0 HcmV?d00001 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers new file mode 100644 index 0000000000000000000000000000000000000000..58e72c446093d4363485c4e90602685f57a1a34e GIT binary patch literal 2579 zcmY+EXH*ji630VHfDl3pMZid}5rYPh-j^y(rNh#KMp|g0NDU?eQd|yHYQRNm=)EaD zbZLr!^j@VYEW&c!y}RAFALh-x-+yL4{*ZVuGc^?r63@^JVvr>25l@(?=%{k=42`sS zhT1D!iNpgv{}BNlXz@UsD{OI9<{-xZT|vN9)H!(CpGZ9I2~q+C{y)BY&Pxk>OJ5hp zA)V`N@@^E&$}h#WjcXsJrU7)C(&A}*4-@u(*N#s*Q(3!FHMywG%qce(-{FNQjbNMicMUV6Uu*TPec#?I%h@A)-ZG$aeiFv+6jTER zEr7jLHk6o&6w@xdif4RN5pYV-41@R+%;S#q&yA~%ES~Iu(sUV0W*?Mi@MC|mf-NP1 z$?(R!+0&Vg-egM}+vq0a-Kp8HSe!R>>vv_KD91XR zt^7j$`a|rTya9VP`T>qujgHj~zNWhD+D;UL%`_>r19xET_q})BgC;H@udyZp(u1Oq z7tZ`o=S4-NSad2(Vta?#jy|x$CDw+k)=A<%?GAs$3FwkiTN_32s|R`ZOv`?!jV&>r zT!jPow??gfF&(n%B{Ti6xadSbUh?e!+(|hhKFLvCMQOtFZt9@K`cR^GJ19<6ba z>pZjZMMM3mouj`O=#$Z$K$QUx;{}p`X8hvQ4nh_>w7Ln-xJgvVpeAJ_kF@CZ_SAATD5M1SZJ5Sy;9ZS~J`-qY zbd+(e9R&SKVv_x3grDcZ`+UT4FiXNciQ@pL18%vZ`8+Q-(Md|ly30z}=0+26GCoG> zY$)2oX9|&U)>IHh_!@8tgQoKjhzThPFL0GoKIW>%c%`OnOCFC&=DXX?aq?p;FcdVn%kwK%E4ix90A3hZ63^GV zgUi!M1QpNHcwls#Z7Oz;&q=Di96B=i#gIpiE&kovWObClhVrSy2GDk>ufTBu@QbZm zg@c&R=y=lQ>JaC(qs3OLS{V4n>(Tq2+++-RO=;v>!w0`x&GztwKhnREQU6MlZ?g{7 z=!KqUcp|)koCC8;=3~7*&hQZuGr_e$B)sN)QYQg+E?6C-@2ywxCZ)V7u0V{KYDD;5 z>g5ODYB!1Fm4<{@m`kT4YhBaE%EA=xrIussyw^)^E~FgeGe!zs+Iv>UVR#MgbA<;x zdE?9$AAz0MzG#2T^x23Eypiu(Cp-J5WsU1qoY|7BPBBBBMCXg8&!tkp?6ZIl6g|*s z!=Q#It*`e8v}J}k5%M21GVq*l@&XT!`|dWId5H%DuJo%Wbr%KpmbE#?3??l|i| zHB(F$)j?Jtp*F3Zhn5>wY6!O2A-%3UsdE+lt zGQecm;4AFiqwQMAwX2bF6 z>A;>CoT+0vbo+aq``^*E2Fd;PSB4X0`eBI>>p+*?;`o<|9gUOIzYV zjI)ZB+m{Uugeif0ji*_`>UCn2at``aRY&S%nJ#2;xL|^0v4vi4-J1w*zMuoosX6^v zQ@Bt#{_WYK0%Wm6Snki{>x&YX$l|QK9)dfQ{Fl0R`Y54jR2wOQ{5u4s|_ezhnbQ92^ldSKvqjrXy z=WT+X1O@RK@`T?fNt{`scesW;2Z*V`Cu?79@6>n+#xnXY(|GcLtG&$=mmlNYRnpcv z5=P|uyaK4CsygzOt{WHPBl?wElJxRA8XF(?L{~ znyY!1I!d*uI-|5WtQMzsy)2_?+db_>uC8-Gi9#L{Fxe~C3oYzc_NE8v3cg`9KUi2d z{GeRt{@dAGgo>q>^kAThB1X1BB45v3VA?&t19BOsC72U#l9oD^EG(ii~yRWl<;+vgxSy_rq5pv;leUEGw6TlJoWLxL( z0o`xuy$h?v@IFGptvX@LTnK%&ef#who1{LNWc85-_2Fh~Ij3jb)Npf_qT)`v8$F-6 zo;F~6u^A`n;G9JmzQN_kv?F5-*RW@(S~p7GwlOp5gf9H&NAF;8y2K+^gC7L^7QuElepYVL*D6 XnHL8^j^D*wb!qnbhfvc{5p(_xQmMVr literal 0 HcmV?d00001 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust new file mode 100644 index 0000000000000000000000000000000000000000..bb624ca8c88988584d4b0e87cd3a1a3af8104738 GIT binary patch literal 1178 zcmV;L1ZDd$f&`WV0Ru3C1WyJDDuzgg_YDCD0ic2eKm>vWJTQU;I52_)GzJMOhDe6@ z4FLxRpn?P?FoFaj0s#Opf&>}{2`Yw2hW8Bt2LUiC1_~;MNQUX zgIMAnnq*2-*ub9F1DOnX0s{cUP=JC1;G1k?NK!07S6Bey@ns7z`^bH(I>~$M;X~TY z6(};5n>n#|zp9Q{XDk__fHBqwIT(@Fp=M4{NEJMyfCyPhuFb71{3df&S15t%5L2!H z?N+4`tY6oyBUk>`*|sQak0tUb^8fVL}0a`E&5JXe0T)5#Pxfaa?v z*i=-Z2%=(u5L{=YYMyuY5HV~~82V^H=p*7l-Ijm+kU_0~5_GOxd@@4?cyq2F+^c<`pfmZvRjGh1((q_9Fre=EM^-Jx7FJ}JX74)wZqIcSJ z6RJ>KbqZvt(#eBIVUy{kQ{Y1$li|cxg4kL?+M!wErm7RWt`|f%?XELro@P&Hw3D<( z;fY1-fW9KS`@R(a6siTuZ{C=BHM_I9pjS`NRDpC&h z?j$`#dxwj#(mXRR^09zJ5UShoY+qV_+Ehyf^O@Q75QmH`h7?!slvvAqGHvLP2ppTJ z{p7kcx!Xvx0SWeuO}r~yx_vF?;FAfL*ZF6T6HlcB__#}|fffJSb`8|Lw?F(V@5iel z^h!2cT?eRnKZH1(-)=*_71d~Rf)(62bR$N4dmRoHu%T$eQNyV6Xf05qy&+qtGn(__I}gi_%lYOfcdnTUvNhk zO|USJcr*Mz;jka}Db+d$$qH`06=jQG`Cj-)@SAR5{f2?v;&Mg%fGHYiI!dKvu5qgz zNdl{7gnQU?zYb)=0K1Y~8qcMfT6^^0$xdk7Y4hV&J9a3E@99S2q|3Pwag^dA7k9x= z()}%sK{Po-fmFO^XWkQ9AFdAkr+?QN4V;3eeE^3U@#D*)$DfA{ zB@>$-eanXAChZbmfaKdl-Q=*?(XfJ|=rk{Lx*C= zDCocl*``*Ys+o%4F8~?KHc8H3wne$VnnaQr-LEF5kyQ9hMNGn3yFfQ!IoRJtuM=IC zgPGN>=Q``cs!QH?B^Xckp0=?qcFl+qR8lJR>e7lSY4O4-F*0aFBA>#~2eYi+RMRoT zU@+90QMD^sL|#L2^gyCfc+VRs>HH9c0O5XEYcM`AAutIB1uG5%0vZJX1Qcp~@iev} s%EsyJ(@eH)6}YwJoz?^tDsFeHFGU8z)D|l9S;NV|VAC9>0s{etpebc4%>V!Z literal 0 HcmV?d00001 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys new file mode 100644 index 0000000000000000000000000000000000000000..e10b65832b5594097fcedb74a5be13cbed78cfd0 GIT binary patch literal 3701 zcmY+EXE+-UyT+4hs6A`86gA7g ztx-Yr?K#&u?>X;>=enQkzMtp*_>;y#fK>fIe&_;a#`^TVa3fU_d45|>1j}56t(F}Cim$^>nwU-ogPWde3Q3TFi*~oOl-*L z@f4=-4b@w`rO=z}&icrsE<&~O+=FvMeN1r~GM~8IMAtu=f&UKh=!@j6L?2?T_jF@V z%AV6llRvH2V5!=q-NVNaJ4WjLQYqeKx=;G`PvWImqXYzIO=^k6#;;rgz7JFPohcqE zHGKL0^rjlld$Z3FLDTx=L}ART9R?zpy7l;Y5H3Sj6ln?V+JY|_9p{v=JLq&lzIVoZ z>qc{pI*X8H|2#NC4=cTHIN7re)XofWR91Wx|p9&^L;CJuTNr?JGQ)3+naJ z+~J)L^$>H(mjI6i@v$djbP!qCYWbWfW+4H(d(YYY$xf6tbpsN3PtdbJYAX_K-MG^v zs_YaVEkFHF6)HCh@A8CuMEke#W>HeP#dZkaqFP65_^5au%05c`WKWJIC^qk5A~L1@ z63rP`6VQvC#d8e`uvEBQ@TY0=PByeD9TI6N_Y#Pq9gt*k*&z=YGOLZt0yZfJZ ztnUa|g`uD=m@EZ`ClJ11Wig^}!r@HLxD7U1>_EIdao@uOXOfA2`g+3Y>C)}9n#94+ z{1g-w)ODMRo&jHyt8^!D0qkt>shx! zXv%U6DLdP>XX$1^yedao0MD}M&vUZR(moBXx!e5WzT>_JqjH)V~$hpPxRE{LlXOeH^QCgK&;=x}AxXR&bAxLLz*0TXAR)7L2 zV!x5wfh!Zk$MM3jtt-}jpYW?^p>nn3d&^O0fTQm?;?Rt4baR0K)Pz>WnoT1F3WCdC zMmgvUa^(m8=&C9J9Dvh|!0X95YSn8Wr4O?3a%#^#`GOOrRs zjbW{!uBd4CU6rUP&3!v^{6T2{vF{=ow?cZ(>TB+hlKQEQ`4RDi^{l<_NWM6 z%l;(|PBx#wAU8JP(T!BvR@hWQm;u?tr#k-9J67;b|P36l&I%wXXY;K?m>Qx1th$jZnoAa5e1aTLY> z?ja{D!%;l>YbAq#fWLzKzXbRnoF)4&&JuKKPBvq_KBVpUOSrh8J9fT+BmbMTk8l)v z+B|`ow!Ua>>iYM0iJBvi7_0i>Mb}0^%SFBNJgVKC)h?eY{9U_QwS=?RESTtbNdq%x zGY(B^9=g;yypiOvr4Cl(47YmCr@@ny<_8nc9|%un1m1D0KYBBd2(0h1yTRqVs@o#K zY0oeiK8o9GV!9TUzLkxI0h?}>u>~$(azSGC*##pMT`=Q?T(8UmORg_h3I0?DE$38M z0e=cqXi>wkCP<#p{Iiwno|E(M4y5E4DQB=$`SpcO8NAVK&AZ!cAHsrmnV)Uw26@)< ziZ$KcH%s2F;NIOyJQWV-P1_KhR&P#Iv61}cnBJ`Ps91-tq-um?J-_FT?Et|2vriKl z$3)!ffVPiffZ?b2K#7LC`ztl9adD^ANaI`0ut%hephe~fANuea7~xgzT~~?YU~8Il z?AGN5?dvxA8IV;tov^{8>~&7_lta7jx2ubJE2n(d`Md4E zau*UW+?ZsXU^`3)V}>1AiEXFFR~p!!Lp3<@B8>`_vso+1+-z5RZ7{2^tM+Fgl`b$H z0IJKNi&agnUmL8MZ-;1^%97EM5F}uEbqY|qKC&Yv@vB$@-cfZ5N)Q0L1E3%cwI#|$BsXY+zG_569yz=Ik9=l<}CF;?M0c4C+I*#(5Cmu zdbu>lo`^Q{Ps6p0a$;Fgsw-wu!%N`~bypQ#K>ioI%6VFkOn+5Qp3!DC^EKmj*@K)J z0u3ihfKnV!KE;i~QmGlum)hmB`%*dj?5hNjTdKHp(g6f7$F{#0luRwoon(e!tK;u8 z9t92s5z2fj{y1%tzEE8bMnQ@QPBqfv9>;FlOtYEpy0mAVU9{VA0aE$>PGpncKOb)D zHm$dp#}V6$naUwI+VeUhxCWowMV>&AyTr!n!`V7qvcoS08} z0a_ckCB#aaHN+++VH5$uUM@kGxEA7_()OnG9W{!_DfFHL^Ic`iD!FF0QoF$4pLx4% zMk_{sL`Wg$9dFisKM{j1w7K`BW2p4yV}$ z3A{a;*hM_)Gs(=)wT(Y9UhchFTF{6>d?6t<^*u7oJj$2hEci42VWK-te9$!1YpTW~N5hO-Y?}>jw(;d(w zqByp#S{fU11Ix13ip7vUBC+T(t19KU$o=O%M%-{2DZlbYIsUPqxYXk*`{IQ8l&kZH zQe_q>@i+k~iv@~isrpNGyB?9#y|;J}_l(uZwW~hUqKh6gvEYNs!Z!BTSZ)j?#A~kQ@cfMi7byP@@0bnt<|*F z-rsM!KJ^`xRq`^u7VhC?8}S|&thxe@I|b0(I7`dQBy*TcHKlp^{0&B{f!!IV3Mve=&q zBS7@^T;HEUf9S3??2OP)r23d?@-SAi|4boCtYVy}i2Egl_GzmOs^EIvJ6bO_Ok26$ z3#wY({DSNcN#@dOnd7-nXnH)BR7fcc=b%^%u0oPTA1J zK*Dp2`%LMBvtdI@8||X*?%jQW$qLd>{EheQL~wV6ZFzg@?DiVk|0zZ#=shiKT;1-M zjN7HET2}yESF4}GB`@f{2TbTWLt;nBUY7&a>h}2uo#PW%h1I&$6n7Z999AJ7ze)>xXkS^F3|K|zAhsyNJqKTs`T3YUWoTj;-bJ?)#fhg9Zz6(Zz-Ey6{% zdpT@kB&~Cr6||kc76SMr#`|)e&b6&pm6}MBTjb8R4k6!pRgcjbEXv#Gm=rB}{it8} z9XI9*i}j9&FdKi2BR^g4dAao}dB9@(M%(Oep&o;VR4p literal 0 HcmV?d00001 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers new file mode 100644 index 0000000000000000000000000000000000000000..0c8e698ab153438a4b54f495d8be87c334147419 GIT binary patch literal 2579 zcmY+EXE+-Q7ss=NgcymS_8u*oSVg6^^@?hY(rRl{yJ+iLH6oOXqQtC<(WaVTf=J5D<(Z!rGv)(=nG~b`U^jAe9KK zfe>Nz6Z{%OWb*!3#AFL0GTEG9tCKQ^vi|Ri4GsiRiI9B^5we4kgTnuhPo4`vxRHI@ zC9|Ajezk6Iwy`p~08;LicOWp}BLPB$wApY@Ee{vbm(MtPd9XQOf3&@7rR9<|o{&S& zpc9zJ5~*ChOTj!>CnJ})we7{DL<5q)hKu#@kg^)WgY-5l6hEKc%{d#;J-D&TwaAz0 z>Vu{Rul|W#jo#`oahdFYF%-8)Gge~z7?qJ#M$)p^eCnFt0{Q_}LOd9}G;z3i)r6yI zM^(FJA^AQsFI9wWwGs@zJ5Uj9_&I&%=z082$`|}KqhCzNoP`Gl>t@PQZdKmnjy!$W z_{jG8ioOHF-wKJyB@y(msi(!0RZ;JmoWSHGEBSIl;>bO*182E98=aprbO6(3De86x z)|PW%;OG5kJoehiIS)tg^-vdi1?<*h!eeS-oGz!=_LmAJ411j$kKDm`A5olV7y2&z zv}wS%0&$@!HgsN`VzesFF*ULBF$3F^7GZzc+jWN;DgTgFDn2qo+!wIE*kN-u+A z{(+HknO4vwwInX((@)btt1x?%w{l4#>#zql|o~D5?>CM z$WxkQS@dLQ{`|wNg>Hlh$B)go(n04#3DvyfmYa5$N$p%%;?9(?@J{`J!ioJz@8oH&(l~rzQAa&s2NDrY`BdYN9%mc;;*>yuHf?k#G zu>mFdd{S4ftPV4Ef-Z@(N9auU!iDIe`)auY^2|VjF#FfR&1ZUY_olGq7V(+O4DKfV zv527eeQyWn_wnDD9v*lF$E3EwZVSp_g{@gUvVbp`??^|Amy9X$EBLg0pk17gl%R5> zZK^N8C3TXQ3)E3JJJV}ok+Ji+1iFckCF0zqW^I033(A#aDtcBwT+O;sO(&?pJ@K7} zMcFeHqK5OZM>yEv=C6GWL+8|xT^H0(BTUa_rshwBMh(aPD!DAxE&5FZtOJ7UgLK^-l}D z_Waw4c-DqaO6nY|h3n7hCJuI!#(>u`o}&*(E1{gEscc(R3`tUbnPbMa>jjw?eBUNm zRMp;=8NCHpor6FR7_H;etJImgkZQ7wquL*`sS`gp4ZrVXeTYkQIIHC8AAaLdPIf@! zcb~6sN%##eYmT6Jg)1VXc_uVJSx2ucAq>OlsQw`@2D=lI=+k2%5|#00a#PsM30;mD zpq1zPKJd5r$g(!~C{s+s$6jcw#pp>1?rv&5#c8D1Xg)Bk3X#E*@B(C()Wt< z?P}#6<0$345zi+~COUSIBQRovSBIIl=vQTo!VrFsb%Hk|s?;N#+er9M$nZT2oQz!j zndypdki(?IWn61Q#0o`;y|R9(OmGjb%J+Qx-8x50>h`r&r?N}WBHsBeZ1wY#GTM=P zDg=U;)1S`81)_oI%!!t^>!qhJ!XPvZw!3xnz8iM%l7KBgZx0!P4qk$$;hOmo;JCEqzP$V>-zSCMcJLW|W5$JL#U|n7 z&MGe;A}vr}-aKcomkLZwVsDf)D;*jkuMTXy9S_Bpy9>#fF6bJ>ui-X}nbx$v9bAxU z)9~N5dVHtLNKOnRn!_yE#TiGtFrM(l+MHP7Rw0l{N{v!p{h%I&h~V8tHZwgz%ETe5 zFv$gh2|lS;;O_Ic<$E>^%d%JCDr4(SfHa*B$%G+XkK$RCjM@HtAtOK5isbOBgh|ce z?2NHV_Y7KStZcy%B?-d%Yd2U$we=bR@ZPlC(5`!LEn$8Zfe4nnF42d zG=QT`kgj>gvms%#+>$Y_CaTvPXFdz);r}=?ez|ejd5}&ZFX&9HBOBLDezOneB#pxE zzZ(ICQ^oa+>Z1D;9i6!2s}vJ?asws_+WVzn+6tFZd-X@{>Y!x&Mfq>3dcqzf0DTm0 zj8U5YxXm#A;SGPVy?Bc0oLuRxvn6*nMP8q)Q_#2ZW~mE%wJqZ9c1w}P+AZ?X6ZEr_ z7*2Y@if(?tQo@tu(o)t_=e;6BxV#qv)FyEMjM8LSWP`RWm+p_~eN*#f)DS$@51B&Oi8}Y@+KUG^9|a6S zkFzYA^;0-xRc!FJ9t}CAfn@`_B|Mtfbs&C{Inax^*6d1XQuOy0!5>OMcvvD=Y`i%7 z>y0mgAZLa$o232F*Tew6^sXST=qeqQalletv;1=(pU+Ef>??sDZV10y4F@ck65xc3 zi|l*R{y&u%u3_?qc}t|q@h`6iH#-k*};AN{=~CR>~GM!zT; zCiO}Go_kqf<%W^cM0!^2F8`r#eY1O-sN3C7mskQhqS7pl1#B(y`nHWp+GMpGr%aNn z=T}liR8gc7*)4AVu&@t4Tbl=RG83KFH&4KiuE_2Q)Jso*Js{FhSwMvnz!kK);x<2%#3j} zT*G;G7M}YdTx7C&ikVSqki~MZn${ePD=A;xNx%X2Qd+}e+@poTv$+~pGV-0sd_GF0 z#lc_em7zac2y+1=hJitu&ai_){7e7{H}ERACX-oyw+P#-*I=KHrdW+4VFPx# literal 0 HcmV?d00001 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust new file mode 100644 index 0000000000000000000000000000000000000000..377ed0c4c4d7631c1f3e53c824dbfc466ae99a17 GIT binary patch literal 1178 zcmV;L1ZDd$f&`WV0Ru3C1WyJDDuzgg_YDCD0ic2eKm>vWJTQU;I52_)GzJMOhDe6@ z4FLxRpn?P?FoFaj0s#Opf&>}{2`Yw2hW8Bt2LUiC1_~;MNQU4l9){enSBfs9>1tbYml9TFJ7l>4Xb!4Z3 zhNw$M=pe4a1V7<{*z$bIk>aaXo6*ENgtde?9oX1HWEY+f%-`b=Sp^1j&OX=Nj4z;lzUv;|FOyeI~82FWud8?-r zIN&Y>F+}#CbLU4BoSG!r4?UCm>x0j;ZBKT)OK@PeaJtzjZF&orc10;Afuv-?DFBad z3->oNYexq}oZHFFUf^99YW}pIE(tLGbx_KJ)$5!wzsLoxt>ZrNVYw|0D7+?02MAp2 z$0qYS`r&tbfwui8k)B=h#l$9?WCmwdc?y>gqe4|JQKYu8pn8=)Fw!&w0ur@hV~8O1$Do=K z-!5QHRG*Rs*9GeK`s-#Vv02^4%4~>7nBgCax42JT1&|iCFM&I-hOs;M#qqCqvw2ovUmN>wG6Ia-&KI+VICFx17Js(Kb16HeX zCTx5Zyb&c})gumqqbPKA+Q5-g%(k3a`Ffp&x#^NX4bpRfle6#l-q&@$B8ce9^EPK` z_H|yMH%EumWDNRV{^(D&_0rLJaOfwfN3LNPO9d}YK-71BdZshOH5c||=9{QCn#jFM zRTjC-(QUA=!T_P_mQxtf5Av^pZxx800CXBb?CKk!{o+d#AmiD1+81X!U_`;e9TtVz zy-Cy({yG}74BrA&nzdc8Rr}M=ph(7(QkZg^*lrqOcIeMHB0cT_8PcuP8j|iTGsNBl z(V2607yLK#y3O}=?xmHT%NCDA<^*5l3d@g~^ONtZZ9}ahMyTL6~4obnQpd18sk~`)O zQNY3T)9LbEjku!G&izfivZ0Tvy}(a^XxkGQQ%%-!<1Olt>*DtSF;rK0xJSB8!ciY+ ztNF`Dm)45}^vGk>ux`;1`jD%2m2iF7vT1a8!CxG3gF3%j+OeFKum{y#fX%iSX?-!y zS4Hk*!{9-j(V<90kZnt{KJ6%ihUHvK;%-k!1N%wfi%4o%<&mEM#F(;ffTX<%mtZwt zNqAutIB1uG5%0vZJX1QaK~m2*yD s`~5+Cbw_IEe!aI70P!%3bIT)GyC;mK*x29&k|#0s{etpcIQQZU6uP literal 0 HcmV?d00001 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json new file mode 100644 index 0000000000..f3f7450945 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json @@ -0,0 +1,34 @@ +[ + { + "op": "add", + "path": "/$defs/telemetry/exporter/properties/type/enum/-", + "value": "stdout" + }, + { + "op": "add", + "path": "/$defs/telemetry/exporter/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "stdout" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "stdout" + } + } + } + } + } +] diff --git a/incubator/exporter-stdout/COPYRIGHT b/incubator/exporter-stdout/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/exporter-stdout/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/exporter-stdout/LICENSE b/incubator/exporter-stdout/LICENSE new file mode 100644 index 0000000000..f3cf11c3ad --- /dev/null +++ b/incubator/exporter-stdout/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/exporter-stdout/NOTICE b/incubator/exporter-stdout/NOTICE new file mode 100644 index 0000000000..9024d8926d --- /dev/null +++ b/incubator/exporter-stdout/NOTICE @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + diff --git a/incubator/exporter-stdout/NOTICE.template b/incubator/exporter-stdout/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/exporter-stdout/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/exporter-stdout/mvnw b/incubator/exporter-stdout/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/exporter-stdout/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/exporter-stdout/mvnw.cmd b/incubator/exporter-stdout/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/exporter-stdout/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/exporter-stdout/pom.xml b/incubator/exporter-stdout/pom.xml new file mode 100644 index 0000000000..58bf155c90 --- /dev/null +++ b/incubator/exporter-stdout/pom.xml @@ -0,0 +1,340 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + exporter-stdout + zilla::incubator::exporter-stdout + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.75 + 0 + + + + + ${project.groupId} + exporter-stdout.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + ${project.groupId} + binding-http.spec + ${project.version} + provided + + + ${project.groupId} + binding-kafka.spec + ${project.version} + provided + + + ${project.groupId} + binding-tcp.spec + ${project.version} + provided + + + ${project.groupId} + binding-tls.spec + ${project.version} + provided + + + ${project.groupId} + catalog-schema-registry.spec + ${project.version} + provided + + + ${project.groupId} + guard-jwt.spec + ${project.version} + provided + + + ${project.groupId} + vault-filesystem.spec + ${project.version} + provided + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + ${project.groupId} + binding-http + ${project.version} + test + + + ${project.groupId} + binding-kafka + ${project.version} + test + + + ${project.groupId} + binding-tcp + ${project.version} + test + + + ${project.groupId} + binding-tls + ${project.version} + test + + + ${project.groupId} + catalog-schema-registry + ${project.version} + test + + + ${project.groupId} + guard-jwt + ${project.version} + test + + + ${project.groupId} + model-json + ${project.version} + test + + + ${project.groupId} + vault-filesystem + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http jwt kafka schema_registry tcp tls + io.aklivity.zilla.runtime.exporter.stdout.internal.types + + + + + generate + + + + + + com.mycila + license-maven-plugin + + + **/org.mockito.plugins.MockMaker + + + + + maven-checkstyle-plugin + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + exporter-stdout.spec + + + ^\Qio/aklivity/zilla/specs/exporter/stdout/\E + io/aklivity/zilla/runtime/exporter/stdout/internal/ + + + + + io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json + ${project.build.directory}/classes + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/exporter/stdout/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java new file mode 100644 index 0000000000..bbc9cfb038 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.io.PrintStream; +import java.lang.reflect.Field; + +import org.agrona.LangUtil; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class StdoutConfiguration extends Configuration +{ + private static final ConfigurationDef STDOUT_CONFIG; + + public static final PropertyDef STDOUT_OUTPUT; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.exporter.stdout"); + STDOUT_OUTPUT = config.property(PrintStream.class, "output", + StdoutConfiguration::decodeOutput, c -> System.out); + STDOUT_CONFIG = config; + } + + public StdoutConfiguration( + Configuration config) + { + super(STDOUT_CONFIG, config); + } + + public PrintStream output() + { + return STDOUT_OUTPUT.get(this); + } + + private static PrintStream decodeOutput( + Configuration config, + String value) + { + try + { + int fieldAt = value.lastIndexOf("."); + Class ownerClass = Class.forName(value.substring(0, fieldAt)); + String fieldName = value.substring(fieldAt + 1); + Field field = ownerClass.getDeclaredField(fieldName); + return (PrintStream) field.get(null); + } + catch (Throwable ex) + { + LangUtil.rethrowUnchecked(ex); + } + return null; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java new file mode 100644 index 0000000000..3e606c2d64 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; + +public class StdoutExporter implements Exporter +{ + public static final String NAME = "stdout"; + + private final StdoutConfiguration config; + + public StdoutExporter( + StdoutConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/stdout.schema.patch.json"); + } + + @Override + public ExporterContext supply( + EngineContext context) + { + return new StdoutExporterContext(config, context); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java new file mode 100644 index 0000000000..bb8d7d3adb --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.util.List; +import java.util.function.LongFunction; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; +import io.aklivity.zilla.runtime.engine.config.AttributeConfig; +import io.aklivity.zilla.runtime.engine.config.ExporterConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterHandler; +import io.aklivity.zilla.runtime.engine.metrics.Collector; +import io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutExporterConfig; + +public class StdoutExporterContext implements ExporterContext +{ + private final StdoutConfiguration config; + private final EngineContext context; + + public StdoutExporterContext( + StdoutConfiguration config, + EngineContext context) + { + this.config = config; + this.context = context; + } + + @Override + public ExporterHandler attach( + ExporterConfig exporter, + List attributes, + Collector collector, + LongFunction resolveKind) + { + StdoutExporterConfig stdoutExporter = new StdoutExporterConfig(exporter); + return new StdoutExporterHandler(config, context, stdoutExporter); + } + + @Override + public void detach( + long exporterId) + { + } + + public String supplyQName( + long namespacedId) + { + return context.supplyQName(namespacedId); + } + + public int supplyTypeId( + String name) + { + return context.supplyTypeId(name); + } + + public MessageReader supplyEventReader() + { + return context.supplyEventReader(); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java new file mode 100644 index 0000000000..a22c55f81f --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi; + +public class StdoutExporterFactorySpi implements ExporterFactorySpi +{ + @Override + public String type() + { + return StdoutExporter.NAME; + } + + @Override + public Exporter create( + Configuration config) + { + return new StdoutExporter(new StdoutConfiguration(config)); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java new file mode 100644 index 0000000000..98a0ed322e --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.io.PrintStream; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterHandler; +import io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutExporterConfig; +import io.aklivity.zilla.runtime.exporter.stdout.internal.stream.StdoutEventsStream; + +public class StdoutExporterHandler implements ExporterHandler +{ + private final StdoutExporterContext context; + private final PrintStream out; + + private StdoutEventsStream events; + + public StdoutExporterHandler( + StdoutConfiguration config, + EngineContext context, + StdoutExporterConfig exporter) + { + this.context = new StdoutExporterContext(config, context); + this.out = config.output(); + } + + @Override + public void start() + { + events = new StdoutEventsStream(context, out); + } + + @Override + public int export() + { + return events.process(); + } + + @Override + public void stop() + { + this.events = null; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java new file mode 100644 index 0000000000..8f9f7ca38a --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import io.aklivity.zilla.runtime.engine.config.ExporterConfig; + +public class StdoutExporterConfig +{ + private final StdoutOptionsConfig options; + + public StdoutExporterConfig( + ExporterConfig exporter) + { + this.options = (StdoutOptionsConfig)exporter.options; + } + + public StdoutOptionsConfig options() + { + return options; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java new file mode 100644 index 0000000000..43f11bb071 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public class StdoutOptionsConfig extends OptionsConfig +{ +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java new file mode 100644 index 0000000000..fb2bbf8068 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi.Kind.EXPORTER; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporter; + +public class StdoutOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + @Override + public Kind kind() + { + return EXPORTER; + } + + @Override + public String type() + { + return StdoutExporter.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + JsonObjectBuilder object = Json.createObjectBuilder(); + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + return new StdoutOptionsConfig(); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java new file mode 100644 index 0000000000..f8f2a84e8b --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.StringFW; + +public abstract class EventHandler +{ + protected static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z"); + + protected final StdoutExporterContext context; + protected final PrintStream out; + + public EventHandler( + StdoutExporterContext context, + PrintStream out) + { + this.context = context; + this.out = out; + } + + public abstract void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length); + + protected static String asString( + StringFW stringFW) + { + String s = stringFW.asString(); + return s == null ? "" : s; + } + + protected static String identity( + StringFW identity) + { + int length = identity.length(); + return length <= 0 ? "-" : identity.asString(); + } + + protected static String asDateTime( + long timestamp) + { + Instant instant = Instant.ofEpochMilli(timestamp); + OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(instant, ZoneId.systemDefault()); + return offsetDateTime.format(FORMATTER); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java new file mode 100644 index 0000000000..c6e330f377 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; +import org.agrona.collections.Int2ObjectHashMap; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; + +public class StdoutEventsStream +{ + private final MessageReader readEvent; + private final Int2ObjectHashMap eventHandlers; + + public StdoutEventsStream( + StdoutExporterContext context, + PrintStream out) + { + this.readEvent = context.supplyEventReader(); + + final Int2ObjectHashMap eventHandlers = new Int2ObjectHashMap<>(); + eventHandlers.put(context.supplyTypeId("http"), new StdoutHttpHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("jwt"), new StdoutJwtHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("kafka"), new StdoutKafkaHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("schema-registry"), + new StdoutSchemaRegistryHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("tcp"), new StdoutTcpHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("tls"), new StdoutTlsHandler(context, out)::handleEvent); + this.eventHandlers = eventHandlers; + } + + public int process() + { + return readEvent.read(this::handleEvent, 1); + } + + private void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final MessageConsumer handler = eventHandlers.get(msgTypeId); + if (handler != null) + { + handler.accept(msgTypeId, buffer, index, length); + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java new file mode 100644 index 0000000000..86dabe799b --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.HttpEventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.HttpRequestAcceptedFW; + +public class StdoutHttpHandler extends EventHandler +{ + private static final String REQUEST_ACCEPTED_FORMAT = "%s %s [%s] REQUEST_ACCEPTED %s %s %s %s%n"; + + private final HttpEventFW httpEventRO = new HttpEventFW(); + + public StdoutHttpHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final HttpEventFW event = httpEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case REQUEST_ACCEPTED: + { + HttpRequestAcceptedFW e = event.requestAccepted(); + String qname = context.supplyQName(e.namespacedId()); + out.format(REQUEST_ACCEPTED_FORMAT, qname, identity(e.identity()), asDateTime(e.timestamp()), asString(e.scheme()), + asString(e.method()), asString(e.authority()), asString(e.path())); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java new file mode 100644 index 0000000000..7acf1ee0fe --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.JwtAuthorizationFailedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.JwtEventFW; + +public class StdoutJwtHandler extends EventHandler +{ + private static final String AUTHORIZATION_FAILED_FORMAT = "%s %s [%s] AUTHORIZATION_FAILED%n"; + + private final JwtEventFW jwtEventRO = new JwtEventFW(); + + public StdoutJwtHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + JwtEventFW event = jwtEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case AUTHORIZATION_FAILED: + JwtAuthorizationFailedFW e = event.authorizationFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(AUTHORIZATION_FAILED_FORMAT, qname, identity(e.identity()), asDateTime(e.timestamp())); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java new file mode 100644 index 0000000000..bae2026051 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.EventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.KafkaApiVersionRejectedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.KafkaEventFW; + +public class StdoutKafkaHandler extends EventHandler +{ + private static final String AUTHORIZATION_FAILED_FORMAT = "%s - [%s] AUTHORIZATION_FAILED%n"; + private static final String API_VERSION_REJECTED_FORMAT = "%s - [%s] API_VERSION_REJECTED %d %d%n"; + + private final KafkaEventFW kafkaEventRO = new KafkaEventFW(); + + public StdoutKafkaHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final KafkaEventFW event = kafkaEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case AUTHORIZATION_FAILED: + { + EventFW e = event.authorizationFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(AUTHORIZATION_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case API_VERSION_REJECTED: + { + KafkaApiVersionRejectedFW e = event.apiVersionRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(API_VERSION_REJECTED_FORMAT, qname, asDateTime(e.timestamp()), e.apiKey(), e.apiVersion()); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java new file mode 100644 index 0000000000..376826b0fd --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.SchemaRegistryEventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.SchemaRegistryRemoteAccessRejectedFW; + +public class StdoutSchemaRegistryHandler extends EventHandler +{ + private static final String REMOTE_ACCESS_REJECTED = "%s - [%s] REMOTE_ACCESS_REJECTED %s %s %d%n"; + + private final SchemaRegistryEventFW schemaRegistryEventRO = new SchemaRegistryEventFW(); + + public StdoutSchemaRegistryHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + SchemaRegistryEventFW event = schemaRegistryEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case REMOTE_ACCESS_REJECTED: + SchemaRegistryRemoteAccessRejectedFW e = event.remoteAccessRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(REMOTE_ACCESS_REJECTED, qname, asDateTime(e.timestamp()), asString(e.method()), asString(e.url()), + e.status()); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java new file mode 100644 index 0000000000..0584a4eddf --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TcpDnsFailedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TcpEventFW; + +public class StdoutTcpHandler extends EventHandler +{ + private static final String DNS_FAILED_FORMAT = "%s - [%s] DNS_FAILED %s%n"; + + private final TcpEventFW tcpEventRO = new TcpEventFW(); + + public StdoutTcpHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final TcpEventFW event = tcpEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case DNS_FAILED: + TcpDnsFailedFW e = event.dnsFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(DNS_FAILED_FORMAT, qname, asDateTime(e.timestamp()), asString(e.address())); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java new file mode 100644 index 0000000000..a3ecce6abf --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.EventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TlsEventFW; + +public class StdoutTlsHandler extends EventHandler +{ + private static final String TLS_FAILED_FORMAT = "%s - [%s] TLS_FAILED%n"; + private static final String PROTOCOL_REJECTED_FORMAT = "%s - [%s] PROTOCOL_REJECTED%n"; + private static final String KEY_REJECTED_FORMAT = "%s - [%s] KEY_REJECTED%n"; + private static final String PEER_NOT_VERIFIED_FORMAT = "%s - [%s] PEER_NOT_VERIFIED%n"; + private static final String HANDSHAKE_FAILED_FORMAT = "%s - [%s] HANDSHAKE_FAILED%n"; + + private final TlsEventFW tlsEventRO = new TlsEventFW(); + + public StdoutTlsHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + TlsEventFW event = tlsEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case TLS_FAILED: + { + EventFW e = event.tlsFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(TLS_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_PROTOCOL_REJECTED: + { + EventFW e = event.tlsProtocolRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(PROTOCOL_REJECTED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_KEY_REJECTED: + { + EventFW e = event.tlsKeyRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(KEY_REJECTED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_PEER_NOT_VERIFIED: + { + EventFW e = event.tlsPeerNotVerified(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(PEER_NOT_VERIFIED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_HANDSHAKE_FAILED: + { + EventFW e = event.tlsHandshakeFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(HANDSHAKE_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/moditect/module-info.java b/incubator/exporter-stdout/src/main/moditect/module-info.java new file mode 100644 index 0000000000..c89775c1c8 --- /dev/null +++ b/incubator/exporter-stdout/src/main/moditect/module-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.exporter.stdout +{ + requires io.aklivity.zilla.runtime.engine; + + provides io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi + with io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutOptionsConfigAdapter; +} diff --git a/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..c0bfc37413 --- /dev/null +++ b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutOptionsConfigAdapter \ No newline at end of file diff --git a/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi new file mode 100644 index 0000000000..8f575d444b --- /dev/null +++ b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterFactorySpi \ No newline at end of file diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java new file mode 100644 index 0000000000..3389e46b3a --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; + +import java.net.URL; + +import org.junit.Test; + +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterFactory; + +public final class StdoutExporterFactoryTest +{ + @Test + public void shouldLoadAndCreate() + { + // GIVEN + Configuration config = new Configuration(); + ExporterFactory factory = ExporterFactory.instantiate(); + + // WHEN + Exporter exporter = factory.create("stdout", config); + ExporterContext context = exporter.supply(mock(EngineContext.class)); + + // THEN + assertThat(exporter, instanceOf(StdoutExporter.class)); + assertThat(exporter.name(), equalTo("stdout")); + assertThat(exporter.type(), instanceOf(URL.class)); + assertThat(context, instanceOf(ExporterContext.class)); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..a0fdab3dbe --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class StdoutOptionsConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new StdoutOptionsConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadOptions() + { + // GIVEN + String text = "{}"; + + // WHEN + StdoutOptionsConfig options = jsonb.fromJson(text, StdoutOptionsConfig.class); + + // THEN + assertThat(options, not(nullValue())); + } + + @Test + public void shouldWriteOptions() + { + // GIVEN + String expectedText = "{}"; + StdoutOptionsConfig config = new StdoutOptionsConfig(); + + // WHEN + String text = jsonb.toJson(config); + + // THEN + assertThat(text, not(nullValue())); + assertThat(text, equalTo(expectedText)); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java new file mode 100644 index 0000000000..c942d6e141 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_BUFFER_SLOT_CAPACITY; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class Http11EventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/message.format") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/message.format"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_BUFFER_SLOT_CAPACITY, 8192) + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/request.with.headers/client", + "${app}/request.with.headers/server" }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void requestWithHeaders() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] REQUEST_ACCEPTED http GET localhost:8080 /\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java new file mode 100644 index 0000000000..e858786c85 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_CONCURRENT_STREAMS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class Http2EventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/message.format") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/message.format"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(HTTP_CONCURRENT_STREAMS, 100) + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v2") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/connection.headers/client", + "${app}/connection.headers/server" }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void connectionHeaders() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] REQUEST_ACCEPTED http GET localhost:8080 /\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java new file mode 100644 index 0000000000..41fa36d119 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_SERVER_HEADER; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class JwtEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/authorization"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(HTTP_SERVER_HEADER, "Zilla") + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.authorization.credentials.yaml") + @Specification({ + "${net}/reject.credentials.header/client", + }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldRejectCredentialsHeader() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 user \\[[^\\]]+\\] AUTHORIZATION_FAILED\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java new file mode 100644 index 0000000000..e1e8c9fda7 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class KafkaEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/kafka/streams/network/group.f1.j5.s3.l3.h3") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/kafka/streams/application/group"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(15, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure("zilla.binding.kafka.client.instance.id", + "io.aklivity.zilla.runtime.exporter.stdout.internal.events.KafkaEventsIT::supplyInstanceId") + .configurationRoot("io/aklivity/zilla/specs/binding/kafka/config") + .external("net0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.yaml") + @Specification({ + "${app}/invalid.describe.config/client", + "${net}/invalid.describe.config/server"}) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldHandleInvalidDescribeConfig() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] API_VERSION_REJECTED 32 0\n")); + } + + public static String supplyInstanceId() + { + return "zilla"; + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java new file mode 100644 index 0000000000..be79fd3ba8 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.matchesPattern; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public final class StdoutOutputRule implements TestRule +{ + public static final PrintStream OUT; + + private static final ByteArrayOutputStream BOS; + + static + { + BOS = new ByteArrayOutputStream(); + OUT = new PrintStream(BOS); + } + + private Pattern expected; + + @Override + public Statement apply( + Statement base, + Description description) + { + return new Statement() + { + @Override + public void evaluate() throws Throwable + { + BOS.reset(); + base.evaluate(); + assertThat(BOS.toString(StandardCharsets.UTF_8), matchesPattern(expected)); + } + }; + } + + public void expect( + Pattern expected) + { + this.expected = expected; + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java new file mode 100644 index 0000000000..989f89eb83 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class TcpEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/tcp/streams/network/rfc793") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/tcp/streams/application/rfc793"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/tcp/config") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.host.yaml") + @Specification({ + "${app}/connection.failed/client" + }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + @Configure(name = "zilla.engine.host.resolver", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.TcpEventsIT::resolveHost") + public void dnsResolutionFailed() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] DNS_FAILED localhost\n")); + } + + public static InetAddress[] resolveHost( + String host) throws UnknownHostException + { + throw new UnknownHostException(); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java new file mode 100644 index 0000000000..cb201e110c --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class TlsEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/tls/streams/network") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/tls/streams/application"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configurationRoot("io/aklivity/zilla/specs/binding/tls/config") + .external("app0") + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/client.hello.malformed/client"}) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldResetMalformedClientHello() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] TLS_FAILED\n")); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/server.handshake.timeout/client"}) + @Configure(name = "zilla.binding.tls.handshake.timeout", value = "1") + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldTimeoutHandshake() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] HANDSHAKE_FAILED\n")); + } +} diff --git a/incubator/pom.xml b/incubator/pom.xml index 5a01348fa2..f84010afd5 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -23,6 +23,7 @@ catalog-inline.spec catalog-schema-registry.spec exporter-otlp.spec + exporter-stdout.spec model-avro.spec model-core.spec model-json.spec @@ -41,6 +42,7 @@ command-tune exporter-otlp + exporter-stdout model-avro model-core @@ -100,6 +102,11 @@ exporter-otlp ${project.version} + + ${project.groupId} + exporter-stdout + ${project.version} + ${project.groupId} model-avro diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java new file mode 100644 index 0000000000..f4c0ee0fc1 --- /dev/null +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.http.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; +import java.util.Map; + +import org.agrona.concurrent.AtomicBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.http.internal.types.Array32FW; +import io.aklivity.zilla.runtime.binding.http.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.http.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.http.internal.types.event.HttpEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.guard.GuardHandler; + +public class HttpEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 2048; + private static final String8FW HEADER_SCHEME = new String8FW(":scheme"); + private static final String8FW HEADER_METHOD = new String8FW(":method"); + private static final String8FW HEADER_AUTHORITY = new String8FW(":authority"); + private static final String8FW HEADER_PATH = new String8FW(":path"); + + private final AtomicBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final HttpEventFW.Builder httpEventRW = new HttpEventFW.Builder(); + private final int httpTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public HttpEventContext( + EngineContext context) + { + this.httpTypeId = context.supplyTypeId(HttpBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void requestAccepted( + long traceId, + long bindingId, + GuardHandler guard, + long authorization, + Map headers) + { + String identity = guard == null ? null : guard.identity(authorization); + HttpEventFW event = httpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .requestAccepted(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + .scheme(headers.get(":scheme")) + .method(headers.get(":method")) + .authority(headers.get(":authority")) + .path(headers.get(":path")) + ) + .build(); + eventWriter.accept(httpTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void requestAccepted( + long traceId, + long bindingId, + GuardHandler guard, + long authorization, + Array32FW headers) + { + String identity = guard == null ? null : guard.identity(authorization); + HttpEventFW event = httpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .requestAccepted(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + .scheme(headers.matchFirst(h -> HEADER_SCHEME.equals(h.name())).value().asString()) + .method(headers.matchFirst(h -> HEADER_METHOD.equals(h.name())).value().asString()) + .authority(headers.matchFirst(h -> HEADER_AUTHORITY.equals(h.name())).value().asString()) + .path(headers.matchFirst(h -> HEADER_PATH.equals(h.name())).value().asString()) + ) + .build(); + eventWriter.accept(httpTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java index 39b9bea739..e8f99951e9 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java @@ -84,6 +84,7 @@ import io.aklivity.zilla.runtime.binding.http.config.HttpVersion; import io.aklivity.zilla.runtime.binding.http.internal.HttpBinding; import io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration; +import io.aklivity.zilla.runtime.binding.http.internal.HttpEventContext; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2ContinuationFW; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2DataFW; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2ErrorCode; @@ -544,6 +545,7 @@ public final class HttpServerFactory implements HttpStreamFactory private final Matcher connectionClose; private final int maximumHeadersSize; private final Long2ObjectHashMap bindings; + private final HttpEventContext event; public HttpServerFactory( HttpConfiguration config, @@ -576,6 +578,7 @@ public HttpServerFactory( this.supplyValidator = context::supplyValidator; this.encodeMax = bufferPool.slotCapacity(); this.bindings = new Long2ObjectHashMap<>(); + this.event = new HttpEventContext(context); this.headers200 = initHeaders(config, STATUS_200); this.headers204 = initHeaders(config, STATUS_204); @@ -1043,7 +1046,7 @@ else if (!isCorsRequestAllowed(server.binding, headers)) final String credentialsMatch = server.credentials.apply(headers::get); if (credentialsMatch != null) { - guard.reauthorize(server.initialId, credentialsMatch); + guard.reauthorize(traceId, server.routedId, server.initialId, credentialsMatch); } server.doEncodeHeaders(traceId, authorization, budgetId, headers204); } @@ -1055,7 +1058,7 @@ else if (!isCorsRequestAllowed(server.binding, headers)) final String credentialsMatch = server.credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(server.initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, server.routedId, server.initialId, credentialsMatch); } } @@ -2268,6 +2271,7 @@ private boolean onDecodeHeaders( final HttpHeaderFW connection = beginEx.headers().matchFirst(h -> HEADER_CONNECTION.equals(h.name())); exchange.responseClosing = connection != null && connectionClose.reset(connection.value().asString()).matches(); + event.requestAccepted(traceId, routedId, guard, authorization, beginEx.headers()); this.exchange = exchange; } return headersValid; @@ -4923,7 +4927,7 @@ else if (headersDecoder.httpError()) else { final Map headers = headersDecoder.headers; - + event.requestAccepted(traceId, routedId, guard, authorization, headers); if (isCorsPreflightRequest(headers)) { if (!endRequest) @@ -4956,7 +4960,7 @@ else if (!isCorsRequestAllowed(binding, headers)) final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - guard.reauthorize(initialId, credentialsMatch); + guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } doEncodeHeaders(traceId, authorization, streamId, headers204, true); } @@ -4968,7 +4972,7 @@ else if (!isCorsRequestAllowed(binding, headers)) final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } @@ -5342,7 +5346,7 @@ private void doEncodePromise( final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java new file mode 100644 index 0000000000..86e8bc3168 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.kafka.internal.types.event.KafkaEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class KafkaEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + private static final int ERROR_NONE = 0; + + private final KafkaEventFW.Builder kafkaEventRW = new KafkaEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int kafkaTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public KafkaEventContext( + EngineContext context) + { + this.kafkaTypeId = context.supplyTypeId(KafkaBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void authorizationFailed( + long traceId, + long bindingId) + { + KafkaEventFW event = kafkaEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .authorizationFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(kafkaTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void apiVersionRejected( + long traceId, + long bindingId, + int apiKey, + int apiVersion) + { + KafkaEventFW event = kafkaEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .apiVersionRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .apiKey(apiKey) + .apiVersion(apiVersion) + ) + .build(); + eventWriter.accept(kafkaTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java index f1eaf5b83b..95c1a8dc53 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java @@ -965,6 +965,7 @@ public void onDecodeResource( assert resource.equals(this.topic); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -975,6 +976,15 @@ public void onDecodeResource( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, DESCRIBE_CONFIGS_API_KEY, DESCRIBE_CONFIGS_API_VERSION, + errorCode); + } + private void onNetwork( int msgTypeId, DirectBuffer buffer, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java index f71de6de0d..b44b82d71c 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java @@ -2949,6 +2949,7 @@ private void onDecodeOffsetsPartition( this.nextOffset = partitionOffset; break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); cleanupApplication(traceId, errorCode); doNetworkEnd(traceId, authorization); break; @@ -2988,6 +2989,10 @@ private void onDecodeFetchPartition( traceId, authorization, 0, EMPTY_OCTETS); } } + else + { + onDecodeResponseErrorCode(traceId, originId, errorCode); + } cleanupApplication(traceId, errorCode); doNetworkEnd(traceId, authorization); @@ -2995,6 +3000,14 @@ private void onDecodeFetchPartition( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, FETCH_API_KEY, FETCH_API_VERSION, errorCode); + } + private void onDecodeFetchTransactionAbort( long traceId, long authorization, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java index cee60290bd..820ab60ab5 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java @@ -878,6 +878,8 @@ private int decodeFindCoordinatorResponse( findCoordinatorResponse.host(), findCoordinatorResponse.port()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, FIND_COORDINATOR_API_KEY, + FIND_COORDINATOR_API_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeClusterReject; break; @@ -1012,6 +1014,7 @@ private int decodeJoinGroupResponse( joinGroupResponse.memberId().asString()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, JOIN_GROUP_API_KEY, JOIN_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1071,6 +1074,7 @@ private int decodeSyncGroupResponse( client.onSyncGroupResponse(traceId, authorization, syncGroupResponse.assignment()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, SYNC_GROUP_API_KEY, SYNC_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1133,6 +1137,7 @@ private int decodeHeartbeatResponse( client.onHeartbeatResponse(traceId, authorization); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, HEARTBEAT_API_KEY, HEARTBEAT_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1197,6 +1202,8 @@ private int decodeLeaveGroupResponse( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, LEAVE_GROUP_API_KEY, + LEAVE_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; } @@ -1211,6 +1218,8 @@ private int decodeLeaveGroupResponse( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, LEAVE_GROUP_API_KEY, LEAVE_GROUP_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; } @@ -2568,11 +2577,20 @@ public void onDecodeResource( assert resource.equals(delegate.nodeId); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); onNetworkError(traceId, errorCode); break; } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, DESCRIBE_CONFIGS_API_KEY, DESCRIBE_CONFIGS_API_VERSION, errorCode); + } + private void onNetwork( int msgTypeId, DirectBuffer buffer, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java index 732dc9f7d7..22994bc0c5 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java @@ -1810,6 +1810,7 @@ private void onDecodeTopic( newPartitions.clear(); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -1830,6 +1831,18 @@ private void onDecodePartition( { newPartitions.put(partitionId, leaderId); } + else + { + onDecodeResponseErrorCode(traceId, originId, partitionError); + } + } + + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, METADATA_API_KEY, METADATA_API_VERSION, errorCode); } @Override diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java index 65bd5d6686..04771ff55a 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java @@ -592,6 +592,8 @@ private int decodeOffsetCommitPartition( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, OFFSET_COMMIT_API_KEY, OFFSET_COMMIT_API_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeReject; } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java index 35369efb3d..38c0b38ccb 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java @@ -664,6 +664,8 @@ private int decodeOffsetFetchPartition( client.decoder = decodeOffsetFetchPartitions; break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, OFFSET_FETCH_API_KEY, OFFSET_FETCH_API_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeReject; break; diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java index d21855ab20..9be9f026d2 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java @@ -42,6 +42,7 @@ import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaBinding; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaConfiguration; +import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaEventContext; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaBindingConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaRouteConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Array32FW; @@ -193,6 +194,7 @@ public final class KafkaClientProduceFactory extends KafkaClientSaslHandshaker i private final int decodeMaxBytes; private final int encodeMaxBytes; private final CRC32C crc32c; + private final KafkaEventContext event; public KafkaClientProduceFactory( KafkaConfiguration config, @@ -218,6 +220,7 @@ public KafkaClientProduceFactory( this.encodeMaxBytes = Math.min(config.clientProduceMaxBytes(), encodePool.slotCapacity() - PRODUCE_REQUEST_RECORDS_OFFSET_MAX); this.crc32c = new CRC32C(); + this.event = new KafkaEventContext(context); } @Override @@ -2268,6 +2271,7 @@ private void onDecodeProducePartition( assert partitionId == this.partitionId; break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -2278,6 +2282,14 @@ private void onDecodeProducePartition( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, PRODUCE_API_KEY, PRODUCE_API_VERSION, errorCode); + } + @Override protected void onDecodeSaslResponse( long traceId) diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java index b6a3b266c4..ca61063380 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java @@ -43,6 +43,7 @@ import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; import io.aklivity.zilla.runtime.binding.kafka.identity.KafkaClientIdSupplier; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaConfiguration; +import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaEventContext; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaScramMechanism; import io.aklivity.zilla.runtime.binding.kafka.internal.types.String16FW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.codec.RequestHeaderFW; @@ -64,6 +65,7 @@ public abstract class KafkaClientSaslHandshaker private static final short SASL_AUTHENTICATE_API_VERSION = 1; private static final int ERROR_SASL_AUTHENTICATION_FAILED = 58; private static final int ERROR_NONE = 0; + private static final int ERROR_UNSUPPORTED_VERSION = 35; private static final String CLIENT_KEY = "Client Key"; private static final String SERVER_KEY = "Server Key"; @@ -92,6 +94,7 @@ public abstract class KafkaClientSaslHandshaker private final SaslHandshakeResponseFW saslHandshakeResponseRO = new SaslHandshakeResponseFW(); private final SaslHandshakeMechanismResponseFW saslHandshakeMechanismResponseRO = new SaslHandshakeMechanismResponseFW(); private final SaslAuthenticateResponseFW saslAuthenticateResponseRO = new SaslAuthenticateResponseFW(); + private final KafkaEventContext event; private KafkaSaslClientDecoder decodeSaslPlainAuthenticate = this::decodeSaslPlainAuthenticate; private KafkaSaslClientDecoder decodeSaslScramAuthenticateFirst = this::decodeSaslScramAuthenticateFirst; @@ -125,6 +128,7 @@ public KafkaClientSaslHandshaker( this.writeBuffer = new UnsafeBuffer(new byte[context.writeBuffer().capacity()]); this.nonceSupplier = config.nonceSupplier(); this.clientIdsByServer = new Object2ObjectHashMap<>(); + this.event = new KafkaEventContext(context); } public abstract class KafkaSaslClient @@ -423,6 +427,19 @@ private void doEncodeSaslScramFinalAuthenticateRequest( doDecodeSaslAuthenticateResponse(traceId); } + protected final void onDecodeResponseErrorCode( + long traceId, + long bindingId, + int apiKey, + int apiVersion, + int errorCode) + { + if (errorCode == ERROR_UNSUPPORTED_VERSION) + { + event.apiVersionRejected(traceId, bindingId, apiKey, apiVersion); + } + } + protected abstract void doNetworkData( long traceId, long budgetId, @@ -689,6 +706,10 @@ private int decodeSaslPlainAuthenticate( if (authenticateResponse != null) { final int errorCode = authenticateResponse.errorCode(); + if (errorCode != ERROR_NONE) + { + event.authorizationFailed(traceId, client.originId); + } progress = authenticateResponse.limit(); @@ -723,6 +744,10 @@ private int decodeSaslScramAuthenticateFirst( if (authenticateResponse != null) { final int errorCode = authenticateResponse.errorCode(); + if (errorCode != ERROR_NONE) + { + event.authorizationFailed(traceId, client.originId); + } progress = authenticateResponse.limit(); diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java index 1c681d82ca..7d8e6a7f5b 100644 --- a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java +++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java @@ -2910,7 +2910,7 @@ else if (this.authField.equals(MqttConnectProperty.PASSWORD)) if (credentialsMatch != null) { - sessionAuth = guard.reauthorize(initialId, credentialsMatch); + sessionAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } diff --git a/runtime/binding-tcp/pom.xml b/runtime/binding-tcp/pom.xml index c28cdc4b4e..513e5eae6b 100644 --- a/runtime/binding-tcp/pom.xml +++ b/runtime/binding-tcp/pom.xml @@ -26,7 +26,7 @@ 11 11 - 0.89 + 0.88 0 @@ -123,7 +123,7 @@ flyweight-maven-plugin ${project.version} - core proxy + core tcp proxy io.aklivity.zilla.runtime.binding.tcp.internal.types diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java new file mode 100644 index 0000000000..7159725444 --- /dev/null +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.tcp.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.tcp.internal.types.event.TcpEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class TcpEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final TcpEventFW.Builder tcpEventRW = new TcpEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int tcpTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public TcpEventContext( + EngineContext context) + { + this.tcpTypeId = context.supplyTypeId(TcpBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void dnsResolutionFailed( + long traceId, + long bindingId, + String address) + { + TcpEventFW event = tcpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .dnsFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .address(address) + ) + .build(); + eventWriter.accept(tcpTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java index 08afe896f8..cc3d8645f9 100644 --- a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java @@ -138,6 +138,7 @@ public MessageConsumer newStream( MessageConsumer application) { final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long traceId = begin.traceId(); final long originId = begin.originId(); final long routedId = begin.routedId(); final long authorization = begin.authorization(); @@ -151,7 +152,7 @@ public MessageConsumer newStream( TcpBindingConfig binding = router.lookup(routedId); if (binding != null) { - route = router.resolve(binding, authorization, beginEx); + route = router.resolve(binding, traceId, authorization, beginEx); } MessageConsumer newStream = null; @@ -257,13 +258,14 @@ private void doNetConnect( state = TcpState.openingInitial(state); net.setOption(SO_KEEPALIVE, options != null && options.keepalive); + networkKey = supplyPollerKey.apply(net); + if (net.connect(remoteAddress)) { onNetConnected(); } else { - networkKey = supplyPollerKey.apply(net); networkKey.handler(OP_CONNECT, this::onNetConnect); networkKey.register(OP_CONNECT); } @@ -283,7 +285,7 @@ private int onNetConnect( net.finishConnect(); onNetConnected(); } - catch (UnresolvedAddressException | IOException ex) + catch (IOException ex) { onNetRejected(); } diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java index ecfb709157..fc343d4e2b 100644 --- a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java @@ -30,6 +30,7 @@ import org.agrona.collections.Long2ObjectHashMap; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.internal.TcpEventContext; import io.aklivity.zilla.runtime.binding.tcp.internal.config.TcpBindingConfig; import io.aklivity.zilla.runtime.binding.tcp.internal.config.TcpRouteConfig; import io.aklivity.zilla.runtime.binding.tcp.internal.types.Array32FW; @@ -49,12 +50,14 @@ public final class TcpClientRouter private final Function resolveHost; private final Long2ObjectHashMap bindings; + private final TcpEventContext event; public TcpClientRouter( EngineContext context) { this.resolveHost = context::resolveHost; this.bindings = new Long2ObjectHashMap<>(); + this.event = new TcpEventContext(context); } public void attach( @@ -71,6 +74,7 @@ public TcpBindingConfig lookup( public InetSocketAddress resolve( TcpBindingConfig binding, + long traceId, long authorization, ProxyBeginExFW beginEx) { @@ -79,86 +83,106 @@ public InetSocketAddress resolve( InetSocketAddress resolved = null; - if (beginEx == null) - { - resolved = options != null ? new InetSocketAddress(options.host, port) : null; - } - else if (binding.routes == TcpBindingConfig.DEFAULT_CLIENT_ROUTES) - { - ProxyAddressFW address = beginEx.address(); - resolved = resolveInetSocketAddress(address); - } - else + try { - final ProxyAddressFW address = beginEx.address(); - - for (TcpRouteConfig route : binding.routes) + if (beginEx == null) { - if (!route.authorized(authorization)) - { - continue; - } + InetAddress[] addresses = options != null ? resolveHost(options.host) : null; + resolved = addresses != null ? new InetSocketAddress(addresses[0], port) : null; + } + else if (binding.routes == TcpBindingConfig.DEFAULT_CLIENT_ROUTES) + { + ProxyAddressFW address = beginEx.address(); + resolved = resolveInetSocketAddress(address); + } + else + { + final ProxyAddressFW address = beginEx.address(); - Array32FW infos = beginEx.infos(); - ProxyInfoFW authorityInfo = infos.matchFirst(i -> i.kind() == AUTHORITY); - if (authorityInfo != null && route.matchesExplicit(r -> r.authority != null)) + for (TcpRouteConfig route : binding.routes) { - final List authorities = Arrays - .stream(resolveHost.apply(authorityInfo.authority().asString())) - .map(a -> new InetSocketAddress(a, port)) - .collect(Collectors.toList()); + if (!route.authorized(authorization)) + { + continue; + } - for (InetSocketAddress authority : authorities) + Array32FW infos = beginEx.infos(); + ProxyInfoFW authorityInfo = infos.matchFirst(i -> i.kind() == AUTHORITY); + if (authorityInfo != null && route.matchesExplicit(r -> r.authority != null)) { - if (route.matchesExplicit(authority)) + final List authorities = Arrays + .stream(resolveHost(authorityInfo.authority().asString())) + .map(a -> new InetSocketAddress(a, port)) + .collect(Collectors.toList()); + + for (InetSocketAddress authority : authorities) { - resolved = authority; - break; + if (route.matchesExplicit(authority)) + { + resolved = authority; + break; + } } } - } - if (resolved == null) - { - resolved = resolve(address, authorization, route::matchesExplicit); - } + if (resolved == null) + { + resolved = resolve(address, authorization, route::matchesExplicit); + } - if (resolved != null) - { - break; + if (resolved != null) + { + break; + } } - } - - if (resolved == null && - options != null && - options.host != null && - !"*".equals(options.host)) - { - final List host = Arrays - .stream(resolveHost.apply(options.host)) - .map(a -> new InetSocketAddress(a, port)) - .collect(Collectors.toList()); - for (TcpRouteConfig route : binding.routes) + if (resolved == null && + options != null && + options.host != null && + !"*".equals(options.host)) { - if (!route.authorized(authorization)) + final List host = Arrays + .stream(resolveHost(options.host)) + .map(a -> new InetSocketAddress(a, port)) + .collect(Collectors.toList()); + + for (TcpRouteConfig route : binding.routes) { - continue; - } + if (!route.authorized(authorization)) + { + continue; + } - resolved = resolve(address, authorization, host::contains); + resolved = resolve(address, authorization, host::contains); - if (resolved != null) - { - break; + if (resolved != null) + { + break; + } } } } } - + catch (TcpDnsFailedException ex) + { + event.dnsResolutionFailed(traceId, binding.id, ex.hostname); + } return resolved; } + private InetAddress[] resolveHost( + String hostname) + { + try + { + return resolveHost.apply(hostname); + } + catch (Throwable ex) + { + throw new TcpDnsFailedException(ex, hostname); + } + } + public void detach( long bindingId) { @@ -291,4 +315,17 @@ private InetSocketAddress resolveInetSocketAddress( return resolved; } + + private static final class TcpDnsFailedException extends RuntimeException + { + private final String hostname; + + TcpDnsFailedException( + Throwable cause, + String hostname) + { + super(cause); + this.hostname = hostname; + } + } } diff --git a/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java b/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java index cd69d87413..5373187431 100644 --- a/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java +++ b/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java @@ -21,7 +21,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.rules.RuleChain.outerRule; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; @@ -37,6 +39,7 @@ import io.aklivity.zilla.runtime.engine.test.EngineRule; import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; public class ClientIT { @@ -215,6 +218,18 @@ public void connnectionFailed() throws Exception k3po.finish(); } + @Test + @Configuration("client.host.yaml") + @Specification({ + "${app}/connection.failed/client" + }) + @Configure(name = "zilla.engine.host.resolver", + value = "io.aklivity.zilla.runtime.binding.tcp.internal.streams.ClientIT::resolveHost") + public void dnsResolutionFailed() throws Exception + { + k3po.finish(); + } + @Test @Configuration("client.host.yaml") @Specification({ @@ -326,4 +341,10 @@ public void shouldWriteDataAfterReceiveEnd() throws Exception } } } + + public static InetAddress[] resolveHost( + String host) throws UnknownHostException + { + throw new UnknownHostException(); + } } diff --git a/runtime/binding-tls/pom.xml b/runtime/binding-tls/pom.xml index 6b6779e140..1fd6bcd4d1 100644 --- a/runtime/binding-tls/pom.xml +++ b/runtime/binding-tls/pom.xml @@ -26,7 +26,7 @@ 11 11 - 0.76 + 0.75 0 @@ -109,7 +109,7 @@ flyweight-maven-plugin ${project.version} - core proxy protocol + core proxy tls protocol io.aklivity.zilla.runtime.binding.tls.internal.types diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java new file mode 100644 index 0000000000..22722224ea --- /dev/null +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.tls.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.tls.internal.types.event.TlsEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class TlsEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final TlsEventFW.Builder tlsEventRW = new TlsEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int tlsTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public TlsEventContext( + EngineContext context) + { + this.tlsTypeId = context.supplyTypeId(TlsBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void tlsFailed( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsProtocolRejected( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsProtocolRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsKeyRejected( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsKeyRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsPeerNotVerified( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsPeerNotVerified(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsHandshakeFailed( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsHandshakeFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java index fd022f46d0..b111b35b77 100644 --- a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java @@ -37,6 +37,10 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; @@ -44,6 +48,7 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.tls.internal.TlsConfiguration; +import io.aklivity.zilla.runtime.binding.tls.internal.TlsEventContext; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig; import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW; @@ -129,6 +134,7 @@ public final class TlsClientFactory implements TlsStreamFactory private final LongUnaryOperator supplyReplyId; private final int initialPadAdjust; private final Long2ObjectHashMap bindings; + private final TlsEventContext event; private final int decodeMax; private final int handshakeMax; @@ -168,6 +174,7 @@ public TlsClientFactory( this.initialPadAdjust = Math.max(context.bufferPool().slotCapacity() >> 14, 1) * MAXIMUM_HEADER_SIZE; this.bindings = new Long2ObjectHashMap<>(); + this.event = new TlsEventContext(context); this.inNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity()); this.inNetBuffer = new UnsafeBuffer(inNetByteBuffer); this.outNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity() << 1); @@ -568,51 +575,80 @@ private int decodeNotHandshaking( try { - final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesProduced = result.bytesProduced(); - final int bytesConsumed = result.bytesConsumed(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesProduced = result.bytesProduced(); + final int bytesConsumed = result.bytesConsumed(); + + switch (result.getStatus()) { - if (!client.stream.isPresent()) + case BUFFER_UNDERFLOW: + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) { - client.onDecodeHandshakeFinished(traceId, budgetId); + if (!client.stream.isPresent()) + { + client.onDecodeHandshakeFinished(traceId, budgetId); + } } - } - if (bytesProduced == 0) - { - client.decoder = decodeHandshake; - progress += bytesConsumed; - } - else - { - assert bytesConsumed == tlsRecordBytes; - assert bytesProduced <= bytesConsumed : String.format("%d <= %d", bytesProduced, bytesConsumed); + if (bytesProduced == 0) + { + client.decoder = decodeHandshake; + progress += bytesConsumed; + } + else + { + assert bytesConsumed == tlsRecordBytes; + assert bytesProduced <= bytesConsumed : + String.format("%d <= %d", bytesProduced, bytesConsumed); - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); - client.decodableRecordBytes -= bytesConsumed; - assert client.decodableRecordBytes == 0; + client.decodableRecordBytes -= bytesConsumed; + assert client.decodableRecordBytes == 0; - client.decoder = decodeNotHandshakingUnwrapped; + client.decoder = decodeNotHandshakingUnwrapped; + } + break; + case CLOSED: + assert bytesProduced == 0; + client.onDecodeInboundClosed(traceId); + client.decoder = TlsState.replyClosed(client.state) ? decodeIgnoreAll : decodeHandshake; + progress += bytesConsumed; + break; } - break; - case CLOSED: - assert bytesProduced == 0; - client.onDecodeInboundClosed(traceId); - client.decoder = TlsState.replyClosed(client.state) ? decodeIgnoreAll : decodeHandshake; - progress += bytesConsumed; - break; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; } } catch (SSLException ex) @@ -750,37 +786,65 @@ private int decodeHandshakeNeedUnwrap( try { - final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesConsumed = result.bytesConsumed(); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - if (TlsState.replyClosed(client.state)) + final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesConsumed = result.bytesConsumed(); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { + case BUFFER_UNDERFLOW: + if (TlsState.replyClosed(client.state)) + { + client.decoder = decodeIgnoreAll; + } + break; + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + assert bytesProduced == 0; + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + { + client.onDecodeHandshakeFinished(traceId, budgetId); + } + client.decoder = decodeHandshake; + break; + case CLOSED: + assert bytesProduced == 0; + client.onDecodeInboundClosed(traceId); client.decoder = decodeIgnoreAll; + break; } - break; - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - assert bytesProduced == 0; - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) - { - client.onDecodeHandshakeFinished(traceId, budgetId); - } - client.decoder = decodeHandshake; - break; - case CLOSED: - assert bytesProduced == 0; - client.onDecodeInboundClosed(traceId); - client.decoder = decodeIgnoreAll; - break; - } - progress += bytesConsumed; + progress += bytesConsumed; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { @@ -1631,6 +1695,7 @@ private void onNetSignalHandshakeTimeout( final long traceId = signal.traceId(); cleanupNet(traceId); + event.tlsHandshakeFailed(traceId, client.originId); decoder = decodeIgnoreAll; } } @@ -1647,7 +1712,35 @@ private void doNetBegin( try { - tlsEngine.beginHandshake(); + try + { + tlsEngine.beginHandshake(); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { @@ -1998,38 +2091,66 @@ private void doEncodeWrap( try { - loop: - do + try { - final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + loop: + do { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - assert false; - break; - case CLOSED: - assert bytesProduced > 0; - doAppReset(traceId); - state = TlsState.closingReply(state); - break loop; - case OK: - assert bytesProduced > 0 || tlsEngine.isInboundDone(); - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { - if (proactiveReplyBegin) + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + assert false; + break; + case CLOSED: + assert bytesProduced > 0; + doAppReset(traceId); + state = TlsState.closingReply(state); + break loop; + case OK: + assert bytesProduced > 0 || tlsEngine.isInboundDone(); + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) { - onDecodeHandshakeFinished(traceId, budgetId); + if (proactiveReplyBegin) + { + onDecodeHandshakeFinished(traceId, budgetId); + } } + break; } - break; - } - } while (inAppByteBuffer.hasRemaining()); + } while (inAppByteBuffer.hasRemaining()); - final int outNetBytesProduced = outNetByteBuffer.position(); - doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + final int outNetBytesProduced = outNetByteBuffer.position(); + doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java index f78ccc88fb..91d30e9b3d 100644 --- a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java @@ -42,7 +42,10 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLSession; import javax.security.auth.x500.X500Principal; @@ -52,6 +55,7 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.tls.internal.TlsConfiguration; +import io.aklivity.zilla.runtime.binding.tls.internal.TlsEventContext; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig; import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW; @@ -145,6 +149,7 @@ public final class TlsServerFactory implements TlsStreamFactory private final LongUnaryOperator supplyReplyId; private final int replyPadAdjust; private final Long2ObjectHashMap bindings; + private final TlsEventContext event; private final int decodeMax; private final int handshakeMax; @@ -183,6 +188,7 @@ public TlsServerFactory( this.handshakeMax = Math.min(config.handshakeWindowBytes(), decodeMax); this.handshakeTimeoutMillis = SECONDS.toMillis(config.handshakeTimeout()); this.bindings = new Long2ObjectHashMap<>(); + this.event = new TlsEventContext(context); this.inNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity()); this.inNetBuffer = new UnsafeBuffer(inNetByteBuffer); @@ -523,8 +529,36 @@ private int decodeBeforeHandshake( { try { - server.tlsEngine.beginHandshake(); - server.decoder = decodeHandshake; + try + { + server.tlsEngine.beginHandshake(); + server.decoder = decodeHandshake; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; + } } catch (SSLException | RuntimeException ex) { @@ -605,43 +639,72 @@ private int decodeNotHandshaking( try { - final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesProduced = result.bytesProduced(); - final int bytesConsumed = result.bytesConsumed(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - if (bytesProduced == 0) + final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesProduced = result.bytesProduced(); + final int bytesConsumed = result.bytesConsumed(); + + switch (result.getStatus()) { - server.decoder = decodeHandshake; + case BUFFER_UNDERFLOW: + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + if (bytesProduced == 0) + { + server.decoder = decodeHandshake; + progress += bytesConsumed; + } + else + { + assert bytesConsumed == tlsRecordBytes; + assert bytesProduced <= bytesConsumed : + String.format("%d <= %d", bytesProduced, bytesConsumed); + + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); + + server.decodableRecordBytes -= bytesConsumed; + assert server.decodableRecordBytes == 0; + + server.decoder = decodeNotHandshakingUnwrapped; + } + break; + case CLOSED: + assert bytesProduced == 0; + server.onDecodeInboundClosed(traceId); + server.decoder = TlsState.initialClosed(server.state) ? decodeIgnoreAll : decodeHandshake; progress += bytesConsumed; + break; } - else - { - assert bytesConsumed == tlsRecordBytes; - assert bytesProduced <= bytesConsumed : String.format("%d <= %d", bytesProduced, bytesConsumed); - - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); - - server.decodableRecordBytes -= bytesConsumed; - assert server.decodableRecordBytes == 0; - - server.decoder = decodeNotHandshakingUnwrapped; - } - break; - case CLOSED: - assert bytesProduced == 0; - server.onDecodeInboundClosed(traceId); - server.decoder = TlsState.initialClosed(server.state) ? decodeIgnoreAll : decodeHandshake; - progress += bytesConsumed; - break; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; } } catch (SSLException | RuntimeException ex) @@ -779,55 +842,83 @@ private int decodeHandshakeNeedUnwrap( try { - final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesConsumed = result.bytesConsumed(); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - if (TlsState.initialClosed(server.state)) - { - server.decoder = decodeIgnoreAll; - } - break; - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); - if (handshakeStatus == HandshakeStatus.FINISHED) - { - server.onDecodeHandshakeFinished(traceId, budgetId); - } + final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesConsumed = result.bytesConsumed(); + final int bytesProduced = result.bytesProduced(); - if (bytesProduced > 0 && handshakeStatus == HandshakeStatus.FINISHED) - { - final TlsRecordInfoFW tlsRecordInfo = tlsRecordInfoRW - .wrap(buffer, progress, progress + bytesConsumed) - .build(); - final int tlsRecordDataOffset = tlsRecordInfo.limit(); - final int tlsRecordDataLimit = tlsRecordDataOffset + tlsRecordInfo.length(); - - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); - server.decoder = decodeNotHandshakingUnwrapped; - } - else + switch (result.getStatus()) { - server.decoder = decodeHandshake; + case BUFFER_UNDERFLOW: + if (TlsState.initialClosed(server.state)) + { + server.decoder = decodeIgnoreAll; + } + break; + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); + if (handshakeStatus == HandshakeStatus.FINISHED) + { + server.onDecodeHandshakeFinished(traceId, budgetId); + } + + if (bytesProduced > 0 && handshakeStatus == HandshakeStatus.FINISHED) + { + final TlsRecordInfoFW tlsRecordInfo = tlsRecordInfoRW + .wrap(buffer, progress, progress + bytesConsumed) + .build(); + final int tlsRecordDataOffset = tlsRecordInfo.limit(); + final int tlsRecordDataLimit = tlsRecordDataOffset + tlsRecordInfo.length(); + + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); + server.decoder = decodeNotHandshakingUnwrapped; + } + else + { + server.decoder = decodeHandshake; + } + + break; + case CLOSED: + assert bytesProduced == 0; + server.onDecodeInboundClosed(traceId); + server.decoder = decodeIgnoreAll; + break; } - break; - case CLOSED: - assert bytesProduced == 0; - server.onDecodeInboundClosed(traceId); - server.decoder = decodeIgnoreAll; - break; + progress += bytesConsumed; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; } - - progress += bytesConsumed; } catch (SSLException | RuntimeException ex) { @@ -1309,6 +1400,7 @@ private void onNetSignalHandshakeTimeout( handshakeTimeoutFutureId = NO_CANCEL_ID; cleanupNet(traceId); + event.tlsHandshakeFailed(traceId, routedId); decoder = decodeIgnoreAll; } } @@ -1652,36 +1744,64 @@ private void doEncodeWrap( try { - loop: - do + try { - final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + loop: + do { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - assert false; - break; - case CLOSED: - assert bytesProduced > 0; - assert tlsEngine.isOutboundDone(); - stream.ifPresent(s -> s.doAppResetLater(traceId)); - state = TlsState.closingReply(state); - break loop; - case OK: - assert bytesProduced > 0 || tlsEngine.isInboundDone(); - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { - onDecodeHandshakeFinished(traceId, budgetId); + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + assert false; + break; + case CLOSED: + assert bytesProduced > 0; + assert tlsEngine.isOutboundDone(); + stream.ifPresent(s -> s.doAppResetLater(traceId)); + state = TlsState.closingReply(state); + break loop; + case OK: + assert bytesProduced > 0 || tlsEngine.isInboundDone(); + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + { + onDecodeHandshakeFinished(traceId, budgetId); + } + break; } - break; - } - } while (inAppByteBuffer.hasRemaining()); + } while (inAppByteBuffer.hasRemaining()); - final int outNetBytesProduced = outNetByteBuffer.position(); - doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + final int outNetBytesProduced = outNetByteBuffer.position(); + doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, routedId); + throw ex; + } } catch (SSLException | RuntimeException ex) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java index aa9f6edb4d..b89c0bde13 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java @@ -57,6 +57,8 @@ import org.agrona.concurrent.AgentRunner; import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.catalog.Catalog; import io.aklivity.zilla.runtime.engine.config.KindConfig; import io.aklivity.zilla.runtime.engine.exporter.Exporter; @@ -71,6 +73,7 @@ import io.aklivity.zilla.runtime.engine.internal.registry.FileWatcherTask; import io.aklivity.zilla.runtime.engine.internal.registry.HttpWatcherTask; import io.aklivity.zilla.runtime.engine.internal.registry.WatcherTask; +import io.aklivity.zilla.runtime.engine.internal.types.event.EventFW; import io.aklivity.zilla.runtime.engine.metrics.Collector; import io.aklivity.zilla.runtime.engine.metrics.MetricGroup; import io.aklivity.zilla.runtime.engine.model.Model; @@ -160,7 +163,7 @@ public final class Engine implements Collector, AutoCloseable EngineWorker worker = new EngineWorker(config, tasks, labels, errorHandler, tuning::affinity, bindings, exporters, guards, vaults, catalogs, models, metricGroups, - this, coreIndex, readonly); + this, this::supplyEventReader, coreIndex, readonly); workers.add(worker); } this.workers = workers; @@ -493,6 +496,11 @@ public long[][] histogramIds() return worker.histogramIds(); } + public MessageReader supplyEventReader() + { + return new EventReader(); + } + public String supplyLocalName( long namespacedId) { @@ -507,6 +515,48 @@ public int supplyLabelId( return worker.supplyTypeId(label); } + private final class EventReader implements MessageReader + { + private final EventFW eventRO = new EventFW(); + private int minWorkerIndex; + private long minTimeStamp; + + @Override + public int read( + MessageConsumer handler, + int messageCountLimit) + { + int messagesRead = 0; + boolean empty = false; + while (!empty && messagesRead < messageCountLimit) + { + int eventCount = 0; + minWorkerIndex = 0; + minTimeStamp = Long.MAX_VALUE; + for (int j = 0; j < workers.size(); j++) + { + final int workerIndex = j; + int eventPeeked = workers.get(workerIndex).peekEvent((m, b, i, l) -> + { + eventRO.wrap(b, i, i + l); + if (eventRO.timestamp() < minTimeStamp) + { + minTimeStamp = eventRO.timestamp(); + minWorkerIndex = workerIndex; + } + }); + eventCount += eventPeeked; + } + empty = eventCount == 0; + if (!empty) + { + messagesRead += workers.get(minWorkerIndex).readEvent(handler, 1); + } + } + return messagesRead; + } + } + // visible for testing public final class ContextImpl implements EngineExtContext { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java index 8ef1e2723a..4cd79e9eed 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java @@ -52,6 +52,7 @@ public class EngineConfiguration extends Configuration public static final IntPropertyDef ENGINE_BUFFER_POOL_CAPACITY; public static final IntPropertyDef ENGINE_BUFFER_SLOT_CAPACITY; public static final IntPropertyDef ENGINE_STREAMS_BUFFER_CAPACITY; + public static final IntPropertyDef ENGINE_EVENTS_BUFFER_CAPACITY; public static final IntPropertyDef ENGINE_COUNTERS_BUFFER_CAPACITY; public static final IntPropertyDef ENGINE_BUDGETS_BUFFER_CAPACITY; public static final BooleanPropertyDef ENGINE_TIMESTAMPS; @@ -88,6 +89,8 @@ public class EngineConfiguration extends Configuration ENGINE_BUFFER_SLOT_CAPACITY = config.property("buffer.slot.capacity", 64 * 1024); ENGINE_STREAMS_BUFFER_CAPACITY = config.property("streams.buffer.capacity", EngineConfiguration::defaultStreamsBufferCapacity); + ENGINE_EVENTS_BUFFER_CAPACITY = config.property("events.buffer.capacity", + EngineConfiguration::defaultEventsBufferCapacity); ENGINE_BUDGETS_BUFFER_CAPACITY = config.property("budgets.buffer.capacity", EngineConfiguration::defaultBudgetsBufferCapacity); ENGINE_COUNTERS_BUFFER_CAPACITY = config.property("counters.buffer.capacity", 1024 * 1024); @@ -179,6 +182,11 @@ public int streamsBufferCapacity() return ENGINE_STREAMS_BUFFER_CAPACITY.getAsInt(this); } + public int eventsBufferCapacity() + { + return ENGINE_EVENTS_BUFFER_CAPACITY.getAsInt(this); + } + public int countersBufferCapacity() { return ENGINE_COUNTERS_BUFFER_CAPACITY.getAsInt(this); @@ -276,6 +284,12 @@ private static int defaultStreamsBufferCapacity( return ENGINE_BUFFER_SLOT_CAPACITY.get(config) * ENGINE_WORKER_CAPACITY.getAsInt(config); } + private static int defaultEventsBufferCapacity( + Configuration config) + { + return ENGINE_BUFFER_SLOT_CAPACITY.get(config) * ENGINE_WORKER_CAPACITY.getAsInt(config); + } + private static int defaultBudgetsBufferCapacity( Configuration config) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java index 2f113352a8..0360890834 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java @@ -18,12 +18,14 @@ import java.net.InetAddress; import java.net.URL; import java.nio.channels.SelectableChannel; +import java.time.Clock; import java.util.function.LongSupplier; import org.agrona.MutableDirectBuffer; import io.aklivity.zilla.runtime.engine.binding.BindingHandler; import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.budget.BudgetCreditor; import io.aklivity.zilla.runtime.engine.budget.BudgetDebitor; import io.aklivity.zilla.runtime.engine.buffer.BufferPool; @@ -117,6 +119,9 @@ String supplyNamespace( String supplyLocalName( long namespacedId); + String supplyQName( + long namespacedId); + BindingHandler streamFactory(); GuardHandler supplyGuard( @@ -148,4 +153,10 @@ void onExporterAttached( void onExporterDetached( long exporterId); + + MessageConsumer supplyEventWriter(); + + MessageReader supplyEventReader(); + + Clock clock(); } diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java new file mode 100644 index 0000000000..9759b940d5 --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.binding.function; + +public interface MessageReader +{ + int read( + MessageConsumer handler, + int messageCountLimit); +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java index 54c3f372fc..a9da2772fe 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java @@ -30,6 +30,8 @@ public interface GuardHandler * @return the session identifier */ long reauthorize( + long traceId, + long bindingId, long contextId, String credentials); diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java new file mode 100644 index 0000000000..ee3670f16b --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayout.java @@ -0,0 +1,173 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.internal.layouts; + +import static io.aklivity.zilla.runtime.engine.internal.spy.RingBufferSpy.SpyPosition.ZERO; +import static org.agrona.IoUtil.createEmptyFile; +import static org.agrona.IoUtil.mapExistingFile; +import static org.agrona.IoUtil.unmap; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.File; +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.agrona.CloseHelper; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.AtomicBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.agrona.concurrent.ringbuffer.OneToOneRingBuffer; +import org.agrona.concurrent.ringbuffer.RingBuffer; +import org.agrona.concurrent.ringbuffer.RingBufferDescriptor; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.internal.spy.OneToOneRingBufferSpy; +import io.aklivity.zilla.runtime.engine.internal.spy.RingBufferSpy; + +public final class EventsLayout implements AutoCloseable +{ + private final Path path; + private final long capacity; + + private RingBuffer buffer; + private RingBufferSpy bufferSpy; + + private EventsLayout( + Path path, + long capacity, + RingBuffer buffer, + RingBufferSpy bufferSpy) + { + this.path = path; + this.capacity = capacity; + this.buffer = buffer; + this.bufferSpy = bufferSpy; + } + + @Override + public void close() + { + unmap(buffer.buffer().byteBuffer()); + } + + public void writeEvent( + int msgTypeId, + DirectBuffer recordBuffer, + int index, + int length) + { + boolean success = buffer.write(msgTypeId, recordBuffer, index, length); + if (!success) + { + rotateFile(); + buffer.write(msgTypeId, recordBuffer, index, length); + } + } + + public int readEvent( + MessageConsumer handler, + int messageCountLimit) + { + return bufferSpy.spy(handler, messageCountLimit); + } + + public int peekEvent( + MessageConsumer handler) + { + return bufferSpy.peek(handler); + } + + private void rotateFile() + { + close(); + String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + Path newPath = Path.of(String.format("%s_%s", path, timestamp)); + try + { + Files.move(path, newPath, StandardCopyOption.REPLACE_EXISTING); + } + catch (IOException ex) + { + ex.printStackTrace(); + rethrowUnchecked(ex); + } + buffer = createRingBuffer(path, capacity); + bufferSpy = createRingBufferSpy(path); + } + + private static AtomicBuffer createAtomicBuffer( + Path path, + long capacity, + boolean createFile) + { + final File layoutFile = path.toFile(); + if (createFile) + { + CloseHelper.close(createEmptyFile(layoutFile, capacity + RingBufferDescriptor.TRAILER_LENGTH)); + } + final MappedByteBuffer mappedBuffer = mapExistingFile(layoutFile, "events"); + return new UnsafeBuffer(mappedBuffer); + } + + private static RingBuffer createRingBuffer( + Path path, + long capacity) + { + AtomicBuffer atomicBuffer = createAtomicBuffer(path, capacity, true); + return new OneToOneRingBuffer(atomicBuffer); + } + + private static RingBufferSpy createRingBufferSpy( + Path path) + { + AtomicBuffer atomicBuffer = createAtomicBuffer(path, 0, false); + OneToOneRingBufferSpy spy = new OneToOneRingBufferSpy(atomicBuffer); + spy.spyAt(ZERO); + return spy; + } + + public static final class Builder + { + private long capacity; + private Path path; + + public Builder capacity( + long capacity) + { + this.capacity = capacity; + return this; + } + + public Builder path( + Path path) + { + this.path = path; + return this; + } + + public EventsLayout build() + { + RingBuffer ringBuffer = createRingBuffer(path, capacity); + RingBufferSpy ringBufferSpy = createRingBufferSpy(path); + return new EventsLayout(path, capacity, ringBuffer, ringBufferSpy); + } + } +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java index 63be6936fb..f07bfe1a2f 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java @@ -42,6 +42,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.channels.SelectableChannel; +import java.time.Clock; import java.time.Duration; import java.util.BitSet; import java.util.Collection; @@ -60,6 +61,7 @@ import java.util.function.LongFunction; import java.util.function.LongSupplier; import java.util.function.LongUnaryOperator; +import java.util.function.Supplier; import org.agrona.DeadlineTimerWheel; import org.agrona.DeadlineTimerWheel.TimerHandler; @@ -86,6 +88,7 @@ import io.aklivity.zilla.runtime.engine.binding.BindingContext; import io.aklivity.zilla.runtime.engine.binding.BindingHandler; import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.budget.BudgetCreditor; import io.aklivity.zilla.runtime.engine.budget.BudgetDebitor; import io.aklivity.zilla.runtime.engine.buffer.BufferPool; @@ -108,6 +111,7 @@ import io.aklivity.zilla.runtime.engine.internal.exporter.ExporterAgent; import io.aklivity.zilla.runtime.engine.internal.layouts.BudgetsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.BufferPoolLayout; +import io.aklivity.zilla.runtime.engine.internal.layouts.EventsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.StreamsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.HistogramsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.ScalarsLayout; @@ -210,6 +214,8 @@ public class EngineWorker implements EngineContext, Agent private final ScalarsLayout countersLayout; private final ScalarsLayout gaugesLayout; private final HistogramsLayout histogramsLayout; + private final EventsLayout eventsLayout; + private final Supplier supplyEventReader; private long initialId; private long promiseId; private long traceId; @@ -232,6 +238,7 @@ public EngineWorker( Collection models, Collection metricGroups, Collector collector, + Supplier supplyEventReader, int index, boolean readonly) { @@ -285,6 +292,11 @@ public EngineWorker( .readonly(readonly) .build(); + this.eventsLayout = new EventsLayout.Builder() + .path(config.directory().resolve(String.format("events%d", index))) + .capacity(config.eventsBufferCapacity()) + .build(); + this.agentName = String.format("engine/data#%d", index); this.streamsLayout = streamsLayout; this.bufferPoolLayout = bufferPoolLayout; @@ -407,6 +419,7 @@ public EngineWorker( this.idleStrategy = idleStrategy; this.errorHandler = errorHandler; this.exportersById = new Long2ObjectHashMap<>(); + this.supplyEventReader = supplyEventReader; } public static int indexOfId( @@ -441,6 +454,14 @@ public String supplyLocalName( return labels.lookupLabel(NamespacedId.localId(namespacedId)); } + @Override + public String supplyQName( + long namespacedId) + { + return String.format("%s.%s", labels.lookupLabel(NamespacedId.namespaceId(namespacedId)), + labels.lookupLabel(NamespacedId.localId(namespacedId))); + } + @Override public int supplyTypeId( String name) @@ -895,6 +916,18 @@ public LongConsumer supplyHistogramWriter( return histogramsLayout.supplyWriter(bindingId, metricId); } + @Override + public MessageConsumer supplyEventWriter() + { + return this.eventsLayout::writeEvent; + } + + @Override + public Clock clock() + { + return Clock.systemUTC(); + } + private void onSystemMessage( int msgTypeId, DirectBuffer buffer, @@ -1557,6 +1590,24 @@ public MessageConsumer supplyReceiver( return writersByIndex.computeIfAbsent(remoteIndex, supplyWriter); } + public int readEvent( + MessageConsumer handler, + int messageCountLimit) + { + return eventsLayout.readEvent(handler, messageCountLimit); + } + + public int peekEvent( + MessageConsumer handler) + { + return eventsLayout.peekEvent(handler); + } + + public MessageReader supplyEventReader() + { + return supplyEventReader.get(); + } + private MessageConsumer supplyWriter( int index) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java new file mode 100644 index 0000000000..4104b505ff --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java @@ -0,0 +1,167 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.internal.spy; + +import static org.agrona.BitUtil.align; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.ALIGNMENT; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.HEADER_LENGTH; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.lengthOffset; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.typeOffset; +import static org.agrona.concurrent.ringbuffer.RingBuffer.PADDING_MSG_TYPE_ID; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.HEAD_POSITION_OFFSET; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TAIL_POSITION_OFFSET; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.checkCapacity; + +import java.util.concurrent.atomic.AtomicLong; + +import org.agrona.DirectBuffer; +import org.agrona.concurrent.AtomicBuffer; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class OneToOneRingBufferSpy implements RingBufferSpy +{ + private final int capacity; + private final AtomicLong spyPosition; + private final AtomicBuffer buffer; + + public OneToOneRingBufferSpy( + final AtomicBuffer buffer) + { + this.buffer = buffer; + checkCapacity(buffer.capacity()); + capacity = buffer.capacity() - TRAILER_LENGTH; + + buffer.verifyAlignment(); + + spyPosition = new AtomicLong(); + } + + @Override + public void spyAt( + SpyPosition position) + { + switch (position) + { + case ZERO: + spyPosition.lazySet(0); + break; + case HEAD: + spyPosition.lazySet(buffer.getLong(capacity + HEAD_POSITION_OFFSET)); + break; + case TAIL: + spyPosition.lazySet(buffer.getLong(capacity + TAIL_POSITION_OFFSET)); + break; + } + } + + @Override + public DirectBuffer buffer() + { + return buffer; + } + + @Override + public long producerPosition() + { + return buffer.getLong(buffer.capacity() - TRAILER_LENGTH + TAIL_POSITION_OFFSET); + } + + @Override + public long consumerPosition() + { + return buffer.getLong(buffer.capacity() - TRAILER_LENGTH + HEAD_POSITION_OFFSET); + } + + @Override + public int spy( + final MessageConsumer handler) + { + return spy(handler, Integer.MAX_VALUE); + } + + @Override + public int spy( + final MessageConsumer handler, + final int messageCountLimit) + { + int messagesRead = 0; + + final AtomicBuffer buffer = this.buffer; + final long head = spyPosition.get(); + + int bytesRead = 0; + + final int capacity = this.capacity; + final int headIndex = (int)head & (capacity - 1); + final int contiguousBlockLength = capacity - headIndex; + + try + { + while (bytesRead < contiguousBlockLength && messagesRead < messageCountLimit) + { + final int recordIndex = headIndex + bytesRead; + final int recordLength = buffer.getIntVolatile(lengthOffset(recordIndex)); + if (recordLength <= 0) + { + break; + } + + bytesRead += align(recordLength, ALIGNMENT); + + final int messageTypeId = buffer.getInt(typeOffset(recordIndex)); + if (PADDING_MSG_TYPE_ID == messageTypeId) + { + continue; + } + + ++messagesRead; + handler.accept(messageTypeId, buffer, recordIndex + HEADER_LENGTH, recordLength - HEADER_LENGTH); + } + } + finally + { + if (bytesRead != 0) + { + spyPosition.lazySet(head + bytesRead); + } + } + + return messagesRead; + } + + public int peek( + final MessageConsumer handler) + { + final AtomicBuffer buffer = this.buffer; + final long head = spyPosition.get(); + final int capacity = this.capacity; + final int headIndex = (int)head & (capacity - 1); + final int recordLength = buffer.getIntVolatile(lengthOffset(headIndex)); + int messagesPeeked = 0; + if (recordLength > 0) + { + final int messageTypeId = buffer.getInt(typeOffset(headIndex)); + if (PADDING_MSG_TYPE_ID != messageTypeId) + { + handler.accept(messageTypeId, buffer, headIndex + HEADER_LENGTH, recordLength - HEADER_LENGTH); + messagesPeeked = 1; + } + } + return messagesPeeked; + } +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java new file mode 100644 index 0000000000..a863b76d1e --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.internal.spy; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public interface RingBufferSpy +{ + enum SpyPosition + { + ZERO, + HEAD, + TAIL + } + + void spyAt(SpyPosition position); + + int spy(MessageConsumer handler); + int spy(MessageConsumer handler, int messageCountLimit); + int peek(MessageConsumer handler); + + long producerPosition(); + long consumerPosition(); + + DirectBuffer buffer(); +} diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java index 711dca56e2..4ae65aba4e 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java @@ -34,6 +34,7 @@ import io.aklivity.zilla.runtime.engine.Engine; import io.aklivity.zilla.runtime.engine.EngineConfiguration; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.ext.EngineExtContext; import io.aklivity.zilla.runtime.engine.ext.EngineExtSpi; @@ -250,6 +251,30 @@ public void shouldNotConfigureUnknownScheme() throws Exception } } + @Test + public void shouldReadEvents() + { + List errors = new LinkedList<>(); + EngineConfiguration config = new EngineConfiguration(properties); + try (Engine engine = Engine.builder() + .config(config) + .errorHandler(errors::add) + .build()) + { + engine.start(); + MessageReader events = engine.supplyEventReader(); + events.read((m, b, i, l) -> {}, 1); + } + catch (Throwable ex) + { + errors.add(ex); + } + finally + { + assertThat(errors, empty()); + } + } + public static final class TestEngineExt implements EngineExtSpi { public static volatile CountDownLatch registerLatch = new CountDownLatch(1); diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java new file mode 100644 index 0000000000..a0aeda974b --- /dev/null +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.internal.layouts; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; + +public class EventsLayoutTest +{ + private static final Path PATH = Paths.get("target/zilla-itests/events0"); + private static final int CAPACITY = 1024; + + private int msgTypeId; + + @Test + public void shouldWriteAndReadEvents() + { + // GIVEN + EventsLayout layout = new EventsLayout.Builder() + .path(PATH) + .capacity(CAPACITY) + .build(); + layout.writeEvent(42, new UnsafeBuffer(), 0, 0); + msgTypeId = 0; + + // WHEN + int count = layout.readEvent(this::readEvent, 1); + + // THEN + assertThat(count, equalTo(1)); + assertThat(msgTypeId, equalTo(42)); + } + + private void readEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + this.msgTypeId = msgTypeId; + } +} diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java similarity index 98% rename from runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java rename to runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java index 9109ab83c2..9d5d5070ae 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.runtime.engine.internal.layout.metrics; +package io.aklivity.zilla.runtime.engine.internal.layouts.metrics; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -28,8 +28,6 @@ import org.junit.Test; -import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.HistogramsLayout; - public class HistogramsLayoutTest { @Test diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java similarity index 96% rename from runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java rename to runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java index bbfdefa615..0a31ac6eb1 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.runtime.engine.internal.layout.metrics; +package io.aklivity.zilla.runtime.engine.internal.layouts.metrics; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -28,8 +28,6 @@ import org.junit.Test; -import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.ScalarsLayout; - public class ScalarsLayoutTest { @Test diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java index 1e872dfcdd..ccc663209e 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java @@ -50,6 +50,8 @@ public TestGuardHandler( @Override public long reauthorize( + long traceId, + long bindingId, long contextId, String credentials) { diff --git a/runtime/guard-jwt/pom.xml b/runtime/guard-jwt/pom.xml index 143b7c3182..fa3544628a 100644 --- a/runtime/guard-jwt/pom.xml +++ b/runtime/guard-jwt/pom.xml @@ -103,6 +103,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core jwt + io.aklivity.zilla.runtime.guard.jwt.internal.types + + + + + generate + + + + com.mycila license-maven-plugin @@ -185,6 +201,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/runtime/guard/jwt/internal/types/**/*.class + BUNDLE diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java new file mode 100644 index 0000000000..99d0fa6146 --- /dev/null +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.guard.jwt.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.guard.jwt.internal.types.event.JwtEventFW; + +public class JwtEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final JwtEventFW.Builder jwtEventRW = new JwtEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int jwtTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public JwtEventContext( + EngineContext context) + { + this.jwtTypeId = context.supplyTypeId(JwtGuard.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void authorizationFailed( + long traceId, + long bindingId, + String identity) + { + JwtEventFW event = jwtEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .authorizationFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + ) + .build(); + eventWriter.accept(jwtTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java index 1412213c25..55c141be78 100644 --- a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java @@ -28,12 +28,14 @@ final class JwtGuardContext implements GuardContext { private final Long2ObjectHashMap handlersById; private final LongSupplier supplyAuthorizedId; + private final EngineContext context; JwtGuardContext( Configuration config, EngineContext context) { this.handlersById = new Long2ObjectHashMap<>(); + this.context = context; this.supplyAuthorizedId = context::supplyAuthorizedId; } @@ -42,7 +44,7 @@ public JwtGuardHandler attach( GuardConfig guard) { JwtOptionsConfig options = (JwtOptionsConfig) guard.options; - JwtGuardHandler handler = new JwtGuardHandler(options, supplyAuthorizedId, guard.readURL); + JwtGuardHandler handler = new JwtGuardHandler(options, context, supplyAuthorizedId, guard.readURL); handlersById.put(guard.id, handler); return handler; } diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java index 54f2358905..b34a2edd36 100644 --- a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java @@ -42,6 +42,7 @@ import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.lang.JoseException; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.guard.GuardHandler; import io.aklivity.zilla.runtime.guard.jwt.config.JwtKeyConfig; import io.aklivity.zilla.runtime.guard.jwt.config.JwtKeySetConfig; @@ -59,9 +60,11 @@ public class JwtGuardHandler implements GuardHandler private final Long2ObjectHashMap sessionsById; private final LongSupplier supplyAuthorizedId; private final Long2ObjectHashMap sessionStoresByContextId; + private final JwtEventContext event; public JwtGuardHandler( JwtOptionsConfig options, + EngineContext context, LongSupplier supplyAuthorizedId, Function readURL) { @@ -113,14 +116,18 @@ public JwtGuardHandler( this.supplyAuthorizedId = supplyAuthorizedId; this.sessionsById = new Long2ObjectHashMap<>(); this.sessionStoresByContextId = new Long2ObjectHashMap<>(); + this.event = new JwtEventContext(context); } @Override public long reauthorize( + long traceId, + long bindingId, long contextId, String credentials) { JwtSession session = null; + String subject = null; authorize: try @@ -147,6 +154,7 @@ public long reauthorize( String payload = signature.getPayload(); JwtClaims claims = JwtClaims.parse(payload); + subject = claims.getSubject(); NumericDate notBefore = claims.getNotBefore(); NumericDate notAfter = claims.getExpirationTime(); String issuer = claims.getIssuer(); @@ -161,7 +169,6 @@ public long reauthorize( break authorize; } - String subject = claims.getSubject(); List roles = Optional.ofNullable(claims.getClaimValue("scope")) .map(s -> s.toString().intern()) .map(s -> s.split("\\s+")) @@ -185,7 +192,10 @@ public long reauthorize( { // not authorized } - + if (session == null) + { + event.authorizationFailed(traceId, bindingId, subject); + } return session != null ? session.authorized : NOT_AUTHORIZED; } diff --git a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java index 88649c6a63..90bd4add79 100644 --- a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java +++ b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java @@ -24,8 +24,11 @@ import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.security.KeyPair; +import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.function.Function; @@ -34,14 +37,27 @@ import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.lang.JoseException; +import org.junit.Before; import org.junit.Test; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; import io.aklivity.zilla.runtime.guard.jwt.config.JwtOptionsConfig; public class JwtGuardHandlerTest { private static final Function READ_KEYS_URL = url -> "{}"; + private EngineContext context; + + @Before + public void init() + { + context = mock(EngineContext.class); + when(context.clock()).thenReturn(mock(Clock.class)); + when(context.supplyEventWriter()).thenReturn(mock(MessageConsumer.class)); + } + @Test public void shouldAuthorize() throws Exception { @@ -53,7 +69,7 @@ public void shouldAuthorize() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -66,7 +82,7 @@ public void shouldAuthorize() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, not(equalTo(0L))); assertThat(guard.identity(sessionId), equalTo("testSubject")); @@ -86,7 +102,7 @@ public void shouldChallengeDuringChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -99,7 +115,7 @@ public void shouldChallengeDuringChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertTrue(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); } @@ -115,7 +131,7 @@ public void shouldNotChallengeDuringWindowWithoutSubject() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -127,7 +143,7 @@ public void shouldNotChallengeDuringWindowWithoutSubject() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertFalse(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); } @@ -143,7 +159,7 @@ public void shouldNotChallengeBeforeChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -156,7 +172,7 @@ public void shouldNotChallengeBeforeChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertFalse(guard.challenge(sessionId, now.plusSeconds(5L).toEpochMilli())); } @@ -172,7 +188,7 @@ public void shouldNotChallengeAgainDuringChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -185,7 +201,7 @@ public void shouldNotChallengeAgainDuringChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertTrue(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); assertFalse(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); @@ -201,7 +217,7 @@ public void shouldNotAuthorizeWhenAlgorithmDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -209,7 +225,7 @@ public void shouldNotAuthorizeWhenAlgorithmDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS512"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -223,7 +239,7 @@ public void shouldNotAuthorizeWhenSignatureInvalid() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -233,7 +249,7 @@ public void shouldNotAuthorizeWhenSignatureInvalid() throws Exception .replaceFirst("\\.[^X]", ".X") .replaceFirst("\\.[^Y]", ".Y"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -247,7 +263,7 @@ public void shouldNotAuthorizeWhenIssuerDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "not test issuer"); @@ -255,7 +271,7 @@ public void shouldNotAuthorizeWhenIssuerDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -269,7 +285,7 @@ public void shouldNotAuthorizeWhenAudienceDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -277,7 +293,7 @@ public void shouldNotAuthorizeWhenAudienceDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -291,7 +307,7 @@ public void shouldNotAuthorizeWhenExpired() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -302,7 +318,7 @@ public void shouldNotAuthorizeWhenExpired() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -316,7 +332,7 @@ public void shouldNotAuthorizeWhenNotYetValid() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -327,7 +343,7 @@ public void shouldNotAuthorizeWhenNotYetValid() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -343,7 +359,7 @@ public void shouldNotVerifyAuthorizedWhenRolesInsufficient() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -352,7 +368,7 @@ public void shouldNotVerifyAuthorizedWhenRolesInsufficient() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, not(equalTo(0L))); assertFalse(guard.verify(sessionId, asList("read:stream", "write:stream"))); @@ -369,7 +385,7 @@ public void shouldReauthorizeWhenExpirationLater() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -382,12 +398,12 @@ public void shouldReauthorizeWhenExpirationLater() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 60L).toMillis())); @@ -404,7 +420,7 @@ public void shouldReauthorizeWhenScopeBroader() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -417,13 +433,13 @@ public void shouldReauthorizeWhenScopeBroader() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); claims.setClaim("scope", "read:stream write:stream"); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 60L).toMillis())); @@ -440,7 +456,7 @@ public void shouldNotReauthorizeWhenExpirationEarlier() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -453,12 +469,12 @@ public void shouldNotReauthorizeWhenExpirationEarlier() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 5L); String tokenPlus5 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus5 = guard.reauthorize(101L, tokenPlus5); + long sessionIdPlus5 = guard.reauthorize(0L, 0L, 101L, tokenPlus5); assertThat(sessionIdPlus5, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -475,7 +491,7 @@ public void shouldNotReauthorizeWhenScopeNarrower() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -488,13 +504,13 @@ public void shouldNotReauthorizeWhenScopeNarrower() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); claims.setClaim("scope", "read:stream"); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -512,7 +528,7 @@ public void shouldNotReauthorizeWhenSubjectDiffers() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -525,13 +541,13 @@ public void shouldNotReauthorizeWhenSubjectDiffers() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("sub", "otherSubject"); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -549,7 +565,7 @@ public void shouldNotReauthorizeWhenContextDiffers() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -562,12 +578,12 @@ public void shouldNotReauthorizeWhenContextDiffers() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(202L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 202L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -585,7 +601,7 @@ public void shouldDeauthorize() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -598,7 +614,7 @@ public void shouldDeauthorize() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); guard.deauthorize(sessionId); } diff --git a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java index 9d54435ab1..1f41b642a8 100644 --- a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java +++ b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java @@ -167,7 +167,7 @@ public void shouldNotVerifyRolesWhenInsufficient() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long authorizedId = handler.reauthorize(101L, token); + long authorizedId = handler.reauthorize(0L, 0L, 101L, token); assertFalse(verifier.test(authorizedId)); } @@ -220,7 +220,7 @@ public void shouldVerifyRolesWhenExact() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -272,7 +272,7 @@ public void shouldVerifyRolesWhenSuperset() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -323,7 +323,7 @@ public void shouldVerifyRolesWhenEmpty() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -374,7 +374,7 @@ public void shouldVerifyWhenIndexDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -425,7 +425,7 @@ public void shouldIdentify() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertEquals("testSubject", identifier.apply(sessionId)); } @@ -478,7 +478,7 @@ public void shouldIdentifyWhenIndexDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertEquals("testSubject", identifier.apply(sessionId)); } diff --git a/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl b/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl index 20887c8bdd..5bffe232f1 100644 --- a/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl +++ b/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl @@ -49,4 +49,26 @@ scope http HttpHeader[] headers; } } + + scope event + { + enum HttpEventType (uint8) + { + REQUEST_ACCEPTED (1) + } + + struct HttpRequestAccepted extends core::event::Event + { + string8 identity; + string8 scheme; + string8 method; + string16 authority; + string16 path; + } + + union HttpEvent switch (HttpEventType) + { + case REQUEST_ACCEPTED: HttpRequestAccepted requestAccepted; + } + } } diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt index 90377a5931..2369372d0d 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt @@ -33,7 +33,7 @@ connected write "GET / HTTP/1.1" "\r\n" "Host: example.com:9090" "\r\n" - "Authorization: Bearer EXPIRED" "\r\n" + "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwODk0MDQ0MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwic3ViIjoidXNlciJ9.KwZt_rDTDEOQ33URwZ8Gd1WqQjAIJESbt5Et309Yz7AJm1wWWyFhWm7AV6lt1rkX-eD-jVh0wHAsy7L4kdzBOErCdwFcdaB4u2jFDGn_9IK28lCedxIYmYtI4qn6eY916IIqRpwZcqzw08OEljfYUKo4UeX7JPAtha0GQmfZY1-NNcncg06xw3xkKSZ1SnIh9MZM1FNH_5QPZPL4NHP7DRXtaMn2w6YpO7n695Sc_3LuSDlfDDMVIUWuEreOzOXem6jheGIbJ-eDKhIXXPrOz1NBAJmvizbugMR7m_bodiEzzqt5ttKDs1974alrR_sYP8OYmR_rCqXc5N3lp_SW3A" "\r\n" "\r\n" write flush diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt index 5761cef887..f091a35b5d 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt @@ -32,7 +32,7 @@ connected read "GET / HTTP/1.1" "\r\n" "Host: example.com:9090" "\r\n" - "Authorization: Bearer EXPIRED" "\r\n" + "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwODk0MDQ0MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwic3ViIjoidXNlciJ9.KwZt_rDTDEOQ33URwZ8Gd1WqQjAIJESbt5Et309Yz7AJm1wWWyFhWm7AV6lt1rkX-eD-jVh0wHAsy7L4kdzBOErCdwFcdaB4u2jFDGn_9IK28lCedxIYmYtI4qn6eY916IIqRpwZcqzw08OEljfYUKo4UeX7JPAtha0GQmfZY1-NNcncg06xw3xkKSZ1SnIh9MZM1FNH_5QPZPL4NHP7DRXtaMn2w6YpO7n695Sc_3LuSDlfDDMVIUWuEreOzOXem6jheGIbJ-eDKhIXXPrOz1NBAJmvizbugMR7m_bodiEzzqt5ttKDs1974alrR_sYP8OYmR_rCqXc5N3lp_SW3A" "\r\n" "\r\n" read closed diff --git a/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl b/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl index 62873faacb..5c66e319b4 100644 --- a/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl +++ b/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl @@ -533,4 +533,25 @@ scope kafka int32 index; } } + + scope event + { + enum KafkaEventType (uint8) + { + AUTHORIZATION_FAILED (1), + API_VERSION_REJECTED (2) + } + + struct KafkaApiVersionRejected extends core::event::Event + { + int32 apiKey; + int32 apiVersion; + } + + union KafkaEvent switch (KafkaEventType) + { + case AUTHORIZATION_FAILED: core::event::Event authorizationFailed; + case API_VERSION_REJECTED: KafkaApiVersionRejected apiVersionRejected; + } + } } diff --git a/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl b/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl index 26730b7747..9431a6b159 100644 --- a/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl +++ b/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl @@ -260,6 +260,5 @@ scope mqtt COMPLETE(0), INCOMPLETE(1) } - } } diff --git a/specs/binding-tcp.spec/pom.xml b/specs/binding-tcp.spec/pom.xml index 3a64fc98ba..94ef266ac7 100644 --- a/specs/binding-tcp.spec/pom.xml +++ b/specs/binding-tcp.spec/pom.xml @@ -73,6 +73,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core tcp proxy + io.aklivity.zilla.specs.binding.tcp.internal.types + + + + + generate + + + + com.mycila license-maven-plugin diff --git a/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl b/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl new file mode 100644 index 0000000000..a796c117f5 --- /dev/null +++ b/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope tcp +{ + scope event + { + enum TcpEventType (uint8) + { + DNS_FAILED (1) + } + + struct TcpDnsFailed extends core::event::Event + { + string16 address; + } + + union TcpEvent switch (TcpEventType) + { + case DNS_FAILED: TcpDnsFailed dnsFailed; + } + } +} diff --git a/specs/binding-tls.spec/pom.xml b/specs/binding-tls.spec/pom.xml index dd0ca5341e..d38dbf81e7 100644 --- a/specs/binding-tls.spec/pom.xml +++ b/specs/binding-tls.spec/pom.xml @@ -78,6 +78,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core tls proxy + io.aklivity.zilla.specs.binding.tls.internal.types + + + + + generate + + + + com.mycila license-maven-plugin diff --git a/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl b/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl new file mode 100644 index 0000000000..c81e96e2c7 --- /dev/null +++ b/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope tls +{ + scope event + { + enum TlsEventType (uint8) + { + TLS_FAILED (1), + TLS_PROTOCOL_REJECTED (2), + TLS_KEY_REJECTED (3), + TLS_PEER_NOT_VERIFIED (4), + TLS_HANDSHAKE_FAILED (5) + } + + union TlsEvent switch (TlsEventType) + { + case TLS_FAILED: core::event::Event tlsFailed; + case TLS_PROTOCOL_REJECTED: core::event::Event tlsProtocolRejected; + case TLS_KEY_REJECTED: core::event::Event tlsKeyRejected; + case TLS_PEER_NOT_VERIFIED: core::event::Event tlsPeerNotVerified; + case TLS_HANDSHAKE_FAILED: core::event::Event tlsHandshakeFailed; + } + } +} diff --git a/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl b/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl index bc78dd0888..05dfc800aa 100644 --- a/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl +++ b/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl @@ -100,4 +100,14 @@ scope core octets extension; } } + + scope event + { + struct Event + { + int64 timestamp; + int64 traceId; + int64 namespacedId; + } + } } diff --git a/specs/guard-jwt.spec/pom.xml b/specs/guard-jwt.spec/pom.xml index ebe02ac328..ab5d123aed 100644 --- a/specs/guard-jwt.spec/pom.xml +++ b/specs/guard-jwt.spec/pom.xml @@ -63,6 +63,23 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core jwt + io.aklivity.zilla.specs.guard.jwt.internal.types + + + + + validate + generate + + + + com.mycila license-maven-plugin @@ -86,6 +103,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/specs/guard/jwt/internal/types/**/*.class + BUNDLE diff --git a/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl b/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl new file mode 100644 index 0000000000..997f9bf859 --- /dev/null +++ b/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +scope jwt +{ + scope event + { + enum JwtEventType (uint8) + { + AUTHORIZATION_FAILED (1) + } + + struct JwtAuthorizationFailed extends core::event::Event + { + string8 identity; + } + + union JwtEvent switch (JwtEventType) + { + case AUTHORIZATION_FAILED: JwtAuthorizationFailed authorizationFailed; + } + } +} From 0f0b3d4a97936fa099bc05dacf1b5002d0c5f070 Mon Sep 17 00:00:00 2001 From: John Fallows Date: Wed, 28 Feb 2024 13:28:17 -0800 Subject: [PATCH 17/25] Add incubating annotation for stdout exporter (#819) --- .../exporter/stdout/internal/StdoutExporterFactorySpi.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java index a22c55f81f..16b7a2e20f 100644 --- a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java @@ -14,10 +14,12 @@ */ package io.aklivity.zilla.runtime.exporter.stdout.internal; +import io.aklivity.zilla.runtime.common.feature.Incubating; import io.aklivity.zilla.runtime.engine.Configuration; import io.aklivity.zilla.runtime.engine.exporter.Exporter; import io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi; +@Incubating public class StdoutExporterFactorySpi implements ExporterFactorySpi { @Override From f0becf83debd2d07c8a2595a13273a49581916bd Mon Sep 17 00:00:00 2001 From: bmaidics Date: Thu, 29 Feb 2024 16:52:44 +0100 Subject: [PATCH 18/25] MQTT-Kafka asyncapi proxy (#818) --- incubator/binding-asyncapi.spec/NOTICE | 1 + incubator/binding-asyncapi.spec/pom.xml | 6 +- .../binding/asyncapi/AsyncapiFunctions.java | 11 +- .../resources/META-INF/zilla/asyncapi.idl | 2 +- .../asyncapi/config/client.http.secure.yaml | 2 +- .../binding/asyncapi/config/client.http.yaml | 2 +- .../asyncapi/config/client.kafka.sasl.yaml | 2 +- .../binding/asyncapi/config/client.kafka.yaml | 2 +- .../asyncapi/config/client.mqtt.secure.yaml | 2 +- .../binding/asyncapi/config/client.mqtt.yaml | 2 +- .../config/kafka/sensor.asyncapi.yaml | 66 + .../asyncapi/config/mqtt/asyncapi.yaml | 16 +- .../asyncapi/config/proxy.mqtt.kafka.yaml | 46 + .../asyncapi/config/server.http.secure.yaml | 2 +- .../binding/asyncapi/config/server.http.yaml | 2 +- .../asyncapi/config/server.mqtt.secure.yaml | 2 +- .../binding/asyncapi/config/server.mqtt.yaml | 2 +- .../schema/asyncapi.schema.patch.json | 135 +- .../asyncapi/http/create.pet/client.rpt | 1 + .../asyncapi/http/create.pet/server.rpt | 1 + .../mqtt/publish.and.subscribe/client.rpt | 3 + .../mqtt/publish.and.subscribe/server.rpt | 3 + .../asyncapi/produce.message/client.rpt | 47 + .../asyncapi/produce.message/server.rpt | 44 + .../asyncapi/proxy.kafka.publish/client.rpt | 62 + .../asyncapi/proxy.kafka.publish/server.rpt | 60 + .../asyncapi/proxy.mqtt.publish/client.rpt | 47 + .../asyncapi/proxy.mqtt.publish/server.rpt | 48 + .../streams/kafka/produce.message/server.rpt | 2 +- .../asyncapi/AsyncapiFunctionsTest.java | 6 +- .../binding/asyncapi/config/SchemaTest.java | 8 + .../asyncapi/streams/asyncapi/AsyncapiIT.java | 20 + incubator/binding-asyncapi/pom.xml | 13 + .../config/AsyncapiChannelsConfig.java | 46 + .../config/AsyncapiChannelsConfigBuilder.java | 69 + .../asyncapi/config/AsyncapiConfig.java | 6 + .../config/AsyncapiMqttKafkaConfig.java | 39 + .../AsyncapiMqttKafkaConfigBuilder.java | 51 + .../config/AsyncapiOptionsConfig.java | 20 +- .../config/AsyncapiOptionsConfigBuilder.java | 21 +- .../internal/AsyncapiBindingAdapter.java | 2 + .../internal/AsyncapiBindingContext.java | 3 + ...AsyncapiClientCompositeBindingAdapter.java | 1 - .../AsyncapiCompositeBindingAdapter.java | 4 +- .../internal/AsyncapiHttpProtocol.java | 2 +- .../AsyncapiProxyCompositeBindingAdapter.java | 150 ++ ...AsyncapiServerCompositeBindingAdapter.java | 2 - .../internal/AyncapiKafkaProtocol.java | 47 +- .../internal/AyncapiMqttProtocol.java | 2 +- .../config/AsyncapiBindingConfig.java | 28 +- .../config/AsyncapiConditionConfig.java | 47 + .../AsyncapiConditionConfigAdapter.java | 58 + .../config/AsyncapiOptionsConfigAdapter.java | 93 +- .../internal/config/AsyncapiRouteConfig.java | 21 +- .../internal/config/AsyncapiWithConfig.java | 15 +- .../config/AsyncapiWithConfigAdapter.java | 72 + .../internal/model/AsyncapiOperation.java | 1 + .../stream/AsyncapiClientFactory.java | 2 +- .../internal/stream/AsyncapiProxyFactory.java | 1764 +++++++++++++++++ .../stream/AsyncapiServerFactory.java | 9 +- .../src/main/moditect/module-info.java | 10 + ...me.engine.config.ConditionConfigAdapterSpi | 1 + ...runtime.engine.config.WithConfigAdapterSpi | 1 + .../AsyncapiBingingFactorySpiTest.java | 6 +- .../AsyncapiConditionConfigAdapterTest.java | 71 + .../AsyncapiOptionsConfigAdapterTest.java | 159 +- .../config/AsyncapiWithConfigAdapterTest.java | 62 + .../internal/stream/proxy/AsyncapiIT.java | 60 + .../config/MqttKafkaConditionConfig.java | 14 +- .../MqttKafkaConditionConfigBuilder.java | 63 + .../config/MqttKafkaOptionsConfig.java | 16 +- .../config/MqttKafkaOptionsConfigBuilder.java | 77 + .../config/MqttKafkaRouteConfig.java | 8 +- .../config/MqttKafkaTopicsConfig.java | 16 +- .../config/MqttKafkaTopicsConfigBuilder.java | 72 + .../kafka/config/MqttKafkaWithConfig.java | 41 + .../config/MqttKafkaWithConfigBuilder.java | 51 + .../config/MqttKafkaBindingConfig.java | 2 + .../MqttKafkaConditionConfigAdapter.java | 25 +- .../config/MqttKafkaOptionsConfigAdapter.java | 19 +- .../config/MqttKafkaWithConfigAdapter.java | 5 +- .../config/MqttKafkaWithResolver.java | 2 + .../stream/MqttKafkaPublishFactory.java | 2 +- .../stream/MqttKafkaSessionFactory.java | 10 +- .../stream/MqttKafkaSubscribeFactory.java | 4 +- .../MqttKafkaConditionConfigAdapterTest.java | 12 +- .../config/MqttKafkaConditionMatcherTest.java | 39 +- .../MqttKafkaOptionsConfigAdapterTest.java | 31 +- .../MqttKafkaWithConfigAdapterTest.java | 4 +- 89 files changed, 3931 insertions(+), 195 deletions(-) create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/sensor.asyncapi.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/proxy.mqtt.kafka.yaml create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/server.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/client.rpt create mode 100644 incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/server.rpt create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfig.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfigBuilder.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfig.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfigBuilder.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfig.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapter.java rename runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfig.java => incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfig.java (66%) create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapter.java create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiProxyFactory.java create mode 100644 incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi create mode 100644 incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi create mode 100644 incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapterTest.java create mode 100644 incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapterTest.java create mode 100644 incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/proxy/AsyncapiIT.java create mode 100644 runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfigBuilder.java rename runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/{internal => }/config/MqttKafkaOptionsConfig.java (68%) create mode 100644 runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfigBuilder.java rename runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/{internal => }/config/MqttKafkaRouteConfig.java (89%) rename runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/{internal => }/config/MqttKafkaTopicsConfig.java (67%) create mode 100644 runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfigBuilder.java create mode 100644 runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfig.java create mode 100644 runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfigBuilder.java diff --git a/incubator/binding-asyncapi.spec/NOTICE b/incubator/binding-asyncapi.spec/NOTICE index 86a7deb7c7..8ac96c59e7 100644 --- a/incubator/binding-asyncapi.spec/NOTICE +++ b/incubator/binding-asyncapi.spec/NOTICE @@ -18,6 +18,7 @@ This project includes: org.leadpony.justify under The Apache Software License, Version 2.0 zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 zilla::specs::binding-kafka.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-mqtt-kafka.spec under Aklivity Community License Agreement zilla::specs::binding-mqtt.spec under The Apache Software License, Version 2.0 zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 zilla::specs::engine.spec under The Apache Software License, Version 2.0 diff --git a/incubator/binding-asyncapi.spec/pom.xml b/incubator/binding-asyncapi.spec/pom.xml index 592757d893..81df8c7969 100644 --- a/incubator/binding-asyncapi.spec/pom.xml +++ b/incubator/binding-asyncapi.spec/pom.xml @@ -14,7 +14,6 @@ binding-asyncapi.spec zilla::incubator::binding-asyncapi.spec - The Apache Software License, Version 2.0 @@ -56,6 +55,11 @@ binding-kafka.spec ${project.version} + + ${project.groupId} + binding-mqtt-kafka.spec + ${project.version} + junit junit diff --git a/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java index b010e97bce..440d91d71c 100644 --- a/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java +++ b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java @@ -59,6 +59,13 @@ public AsyncapiBeginExBuilder typeId( return this; } + public AsyncapiBeginExBuilder apiId( + long apiId) + { + beginExRW.apiId(apiId); + return this; + } + public AsyncapiBeginExBuilder operationId( String operationId) { @@ -89,7 +96,7 @@ public static final class AsyncapiBeginExMatcherBuilder private final AsyncapiBeginExFW beginExRO = new AsyncapiBeginExFW(); private Integer typeId; - private int apiId; + private long apiId; private String operationId; private OctetsFW.Builder extensionRW; @@ -108,7 +115,7 @@ public AsyncapiBeginExMatcherBuilder operationId( } public AsyncapiBeginExMatcherBuilder apiId( - int apiId) + long apiId) { this.apiId = apiId; return this; diff --git a/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl index 5741ec1f8f..22dc7b0946 100644 --- a/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl +++ b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl @@ -19,7 +19,7 @@ scope asyncapi { struct AsyncapiBeginEx extends core::stream::Extension { - int32 apiId = 0; + int64 apiId = 0; string16 operationId = null; octets extension; } diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml index 5c0e2e216e..177a2ed8ed 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml @@ -26,7 +26,7 @@ bindings: vault: test0 options: specs: - - http/asyncapi.yaml + http_api: http/asyncapi.yaml tcp: host: localhost port: diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml index 457b87c52c..246b0e5dfd 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml @@ -22,7 +22,7 @@ bindings: kind: client options: specs: - - http/asyncapi.yaml + http_api: http/asyncapi.yaml tcp: host: localhost port: diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml index 2d31055aee..ded9bd7624 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml @@ -26,7 +26,7 @@ bindings: vault: test0 options: specs: - - kafka/asyncapi.yaml + kafka_api: kafka/asyncapi.yaml tcp: host: localhost port: diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml index 3570121f20..8719d56e79 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml @@ -22,7 +22,7 @@ bindings: kind: client options: specs: - - kafka/asyncapi.yaml + kafka_api: kafka/asyncapi.yaml tcp: host: localhost port: diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml index 47ebc84ec3..ed373d7a29 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml @@ -26,7 +26,7 @@ bindings: vault: test0 options: specs: - - mqtt/asyncapi.yaml + mqtt_api: mqtt/asyncapi.yaml tcp: host: localhost port: diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml index 00b2ea5605..65f9033370 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml @@ -22,7 +22,7 @@ bindings: kind: client options: specs: - - mqtt/asyncapi.yaml + mqtt_api: mqtt/asyncapi.yaml tcp: host: localhost port: diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/sensor.asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/sensor.asyncapi.yaml new file mode 100644 index 0000000000..020633da3f --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/sensor.asyncapi.yaml @@ -0,0 +1,66 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +asyncapi: 3.0.0 +info: + title: Zilla Kafka Proxy + version: 1.0.0 + license: + name: Aklivity Community License +servers: + plain: + host: localhost:9092 + protocol: kafka + +operations: + onSensorData: + action: receive + channel: + $ref: '#/channels/sensorData' + toSensorData: + action: send + channel: + $ref: '#/channels/sensorData' + +channels: + sensorData: + description: This channel contains a message for sensors. + address: sensors + messages: + sensorData: + $ref: '#/components/messages/sensorData' + mqttSessions: + description: This channel contains MQTT sessions. + address: mqtt-sessions + mqttMessages: + description: This channel contains MQTT messages. + address: mqtt-messages + mqttRetained: + description: This channel contains MQTT retained messages. + address: mqtt-retained + +components: + messages: + sensorData: + payload: + type: object + properties: + sensorId: + type: integer + description: This property describes the id of the sensor + message: + type: string + description: This property describes message of the sensor diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml index dd8057f382..7aa9fca2e5 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml @@ -27,12 +27,12 @@ servers: defaultContentType: application/json channels: - smartylighting: - address: "smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured" + sensors: + address: "sensors/{sensorId}" title: MQTT Topic to produce & consume topic. parameters: streetlightId: - $ref: '#/components/parameters/streetlightId' + $ref: '#/components/parameters/sensorId' messages: items: $ref: '#/components/messages/item' @@ -41,17 +41,17 @@ operations: sendEvents: action: send channel: - $ref: '#/channels/smartylighting' + $ref: '#/channels/sensors' receiveEvents: action: receive channel: - $ref: '#/channels/smartylighting' + $ref: '#/channels/sensors' components: parameters: - streetlightId: - description: Street Light ID + sensorId: + description: Sensor ID location: $message.header#/id messages: item: @@ -64,5 +64,5 @@ components: description: Unique identifier for a given event type: string id: - description: Street Light ID + description: Sensor ID type: string diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/proxy.mqtt.kafka.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/proxy.mqtt.kafka.yaml new file mode 100644 index 0000000000..deb7634692 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/proxy.mqtt.kafka.yaml @@ -0,0 +1,46 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + asyncapi_proxy0: + type: asyncapi + kind: proxy + options: + specs: + mqtt_api: mqtt/asyncapi.yaml + kafka_api: kafka/sensor.asyncapi.yaml + mqtt_kafka: + channels: + sessions: mqttSessions + retained: mqttRetained + messages: mqttMessages + routes: + - when: + - api-id: mqtt_api + operation-id: sendEvents + exit: asyncapi_kafka0 + with: + api-id: kafka_api + operation-id: toSensorData + - when: + - api-id: mqtt_api + operation-id: receiveEvents + exit: asyncapi_kafka0 + with: + api-id: kafka_api + operation-id: onSensorData diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml index 3be1dd33a6..a09b6aefd6 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml @@ -26,7 +26,7 @@ bindings: vault: test0 options: specs: - - http/asyncapi.yaml + http_api: http/asyncapi.yaml tls: keys: - localhost diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml index c3d11e19bb..1bb68c32d3 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml @@ -22,5 +22,5 @@ bindings: kind: server options: specs: - - http/asyncapi.yaml + http_api: http/asyncapi.yaml exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml index 25e5d1bcd5..80fee3c5dd 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml @@ -26,7 +26,7 @@ bindings: vault: test0 options: specs: - - mqtt/asyncapi.yaml + mqtt_api: mqtt/asyncapi.yaml tls: keys: - localhost diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml index 8e65b8c21c..36e41f3429 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml @@ -22,5 +22,5 @@ bindings: kind: server options: specs: - - mqtt/asyncapi.yaml + mqtt_api: mqtt/asyncapi.yaml exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json index d06a11b2c7..23e293bfd9 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json @@ -29,7 +29,7 @@ }, "kind": { - "enum": [ "server", "client" ] + "enum": [ "server", "client", "proxy" ] }, "options": { @@ -38,10 +38,18 @@ "specs": { "title": "Specifications", - "type": "array", - "items": + "type": "object", + "apiId": { - "type": "string" + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "type": "string" + } + }, + "maxProperties": 1 } }, "tcp": "#/$defs/binding/tcp/options", @@ -65,11 +73,94 @@ "sasl": "$defs/options/binding/kafka/sasl" }, "additionalProperties": false + }, + "mqtt_kafka": + { + "title": "MQTT-Kafka", + "type": "object", + "properties": + { + "channels": + { + "title": "Channels", + "type": "object", + "properties": + { + "sessions": + { + "title": "Kafka Sessions Channel", + "type": "string" + }, + "messages": + { + "title": "Kafka Messages Channel", + "type": "string" + }, + "retained": + { + "title": "Kafka Retained Channel", + "type": "string" + }, + "additionalProperties": false + }, + "additionalProperties": false + } + }, + "additionalProperties": false } }, "additionalProperties": false }, - "routes": false + "routes": + { + "items": + { + "properties": + { + "when": + { + "items": + { + "additionalProperties": false, + "properties": + { + "api-id": + { + "title": "MQTT API Id", + "type": "string" + }, + "operation-id": + { + "title": "MQTT Operation Id", + "type": "string" + } + } + } + }, + "with": + { + "properties": + { + "api-id": + { + "title": "Kafka API Id", + "type": "string" + }, + "operation-id": + { + "title": "Kafka Operation Id", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "required": + [ + "with" + ] + } + } }, "oneOf": [ @@ -107,6 +198,40 @@ } ] }, + { + "properties": + { + "kind": + { + "const": "proxy" + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "properties": + { + "routes": + { + "required": + [ + "exit" + ] + } + }, + "required": + [ + "routes" + ] + } + ] + }, { "properties": { diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt index 763b53d28c..e169ce5939 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt @@ -20,6 +20,7 @@ connect "zilla://streams/asyncapi0" write zilla:begin.ext ${asyncapi:beginEx() .typeId(zilla:id("asyncapi")) + .apiId(3833148448) .operationId("createPet") .extension(http:beginEx() .typeId(zilla:id("http")) diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt index fd958f606e..daf5dc67dd 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt @@ -22,6 +22,7 @@ accepted read zilla:begin.ext ${asyncapi:matchBeginEx() .typeId(zilla:id("asyncapi")) + .apiId(3833148448) .operationId("createPet") .extension(http:beginEx() .typeId(zilla:id("http")) diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt index 7e16896b71..9c04a2d76f 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt @@ -20,6 +20,7 @@ connect "zilla://streams/asyncapi0" write zilla:begin.ext ${asyncapi:beginEx() .typeId(zilla:id("asyncapi")) + .apiId(3724230511) .extension(mqtt:beginEx() .typeId(zilla:id("mqtt")) .session() @@ -49,6 +50,7 @@ connect "zilla://streams/asyncapi0" write zilla:begin.ext ${asyncapi:beginEx() .typeId(zilla:id("asyncapi")) + .apiId(3724230511) .extension(mqtt:beginEx() .typeId(zilla:id("mqtt")) .publish() @@ -81,6 +83,7 @@ connect "zilla://streams/asyncapi0" write zilla:begin.ext ${asyncapi:beginEx() .typeId(zilla:id("asyncapi")) + .apiId(3724230511) .extension(mqtt:beginEx() .typeId(zilla:id("mqtt")) .subscribe() diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt index 6155cf6cb4..a30cd06c20 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt @@ -22,6 +22,7 @@ accepted read zilla:begin.ext ${asyncapi:matchBeginEx() .typeId(zilla:id("asyncapi")) + .apiId(3724230511) .extension(mqtt:beginEx() .typeId(zilla:id("mqtt")) .session() @@ -50,6 +51,7 @@ accepted read zilla:begin.ext ${asyncapi:matchBeginEx() .typeId(zilla:id("asyncapi")) + .apiId(3724230511) .extension(mqtt:beginEx() .typeId(zilla:id("mqtt")) .publish() @@ -79,6 +81,7 @@ accepted read zilla:begin.ext ${asyncapi:matchBeginEx() .typeId(zilla:id("asyncapi")) + .apiId(3724230511) .extension(mqtt:beginEx() .typeId(zilla:id("mqtt")) .subscribe() diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/client.rpt new file mode 100644 index 0000000000..dbd8c02087 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/client.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .timestamp(newTimestamp) + .partition(0, 1) + .build() + .build()} +write "Hello, world" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/server.rpt new file mode 100644 index 0000000000..e725f5ad6e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/server.rpt @@ -0,0 +1,44 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .partition(0, 1) + .build() + .build()} +read "Hello, world" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/client.rpt new file mode 100644 index 0000000000..4c4c5da8b1 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/client.rpt @@ -0,0 +1,62 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/asyncapi_kafka0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(810158698) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("sensors") + .partition(-1, -2) + .ackMode("NONE") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .deferred(0) + .partition(-1, -1) + .key("sensors/one") + .header("zilla:filter", "sensors") + .header("zilla:filter", "one") + .header("zilla:local", "client") + .headerInt("zilla:expiry", 15) + .header("zilla:content-type", "message") + .header("zilla:format", "TEXT") + .header("zilla:reply-to", "sensors") + .header("zilla:reply-key", "sensors/one") + .header("zilla:reply-filter", "sensors") + .header("zilla:reply-filter", "one") + .header("zilla:correlation-id", "info") + .header("zilla:qos", "0") + .build() + .build()} +write "message" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/server.rpt new file mode 100644 index 0000000000..d8954e59c0 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/server.rpt @@ -0,0 +1,60 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi_kafka0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(810158698) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("sensors") + .partition(-1, -2) + .ackMode("NONE") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .deferred(0) + .partition(-1, -1) + .key("sensors/one") + .header("zilla:filter", "sensors") + .header("zilla:filter", "one") + .header("zilla:local", "client") + .headerInt("zilla:expiry", 15) + .header("zilla:content-type", "message") + .header("zilla:format", "TEXT") + .header("zilla:reply-to", "sensors") + .header("zilla:reply-key", "sensors/one") + .header("zilla:reply-filter", "sensors") + .header("zilla:reply-filter", "one") + .header("zilla:correlation-id", "info") + .header("zilla:qos", "0") + .build() + .build()} +read "message" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/client.rpt new file mode 100644 index 0000000000..a617fcde59 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/client.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/asyncapi_proxy0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensors/one") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("message") + .format("TEXT") + .responseTopic("sensors/one") + .correlation("info") + .build() + .build()} +write "message" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/server.rpt new file mode 100644 index 0000000000..0b74c3634e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/server.rpt @@ -0,0 +1,48 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi_proxy0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensors/one") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("message") + .format("TEXT") + .responseTopic("sensors/one") + .correlation("info") + .build() + .build()} +read "message" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt index 303bd53b17..ea9e2d6bb9 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt @@ -22,7 +22,7 @@ accept ${serverAddress} accepted -read zilla:begin.ext ${kafka:beginEx() +read zilla:begin.ext ${kafka:matchBeginEx() .typeId(zilla:id("kafka")) .merged() .capabilities("PRODUCE_ONLY") diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java index f89d34769e..5970c60168 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java @@ -43,6 +43,7 @@ public void shouldEncodeAsyncapiBeginExt() { final byte[] array = AsyncapiFunctions.beginEx() .typeId(0) + .apiId(1) .operationId("operationId") .extension(new byte[] {1}) .build(); @@ -51,6 +52,7 @@ public void shouldEncodeAsyncapiBeginExt() AsyncapiBeginExFW asyncapiBeginEx = new AsyncapiBeginExFW().wrap(buffer, 0, buffer.capacity()); MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + assertEquals(1, asyncapiBeginEx.apiId()); assertEquals("operationId", asyncapiBeginEx.operationId().asString()); assertEquals(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build(), asyncapiBeginEx.extension()); @@ -64,7 +66,7 @@ public void shouldMatchAsyncapiBeginExtensionOnly() throws Exception .extension(new byte[] {1}) .build(); - ByteBuffer byteBuf = ByteBuffer.allocate(11); + ByteBuffer byteBuf = ByteBuffer.allocate(15); MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); new AsyncapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) @@ -85,7 +87,7 @@ public void shouldMatchAsyncapiBeginExtension() throws Exception .extension(new byte[] {1}) .build(); - ByteBuffer byteBuf = ByteBuffer.allocate(22); + ByteBuffer byteBuf = ByteBuffer.allocate(26); MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); new AsyncapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java index 71f3551cd9..b7b6a8bf6d 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java @@ -113,4 +113,12 @@ public void shouldValidateKafkaClientSasl() assertThat(config, not(nullValue())); } + + @Test + public void shouldValidateAsyncapiProxy() + { + JsonObject config = schema.validate("proxy.mqtt.kafka.yaml"); + + assertThat(config, not(nullValue())); + } } diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java index bfefedfefd..77b4cbaf38 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java @@ -65,4 +65,24 @@ public void shouldProduceMessage() throws Exception { k3po.finish(); } + + @Test + @Specification({ + "${asyncapi}/proxy.kafka.publish/client", + "${asyncapi}/proxy.kafka.publish/server" + }) + public void shouldProxyPublishMessageKafka() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${asyncapi}/proxy.mqtt.publish/client", + "${asyncapi}/proxy.mqtt.publish/server" + }) + public void shouldProxyPublishMessageMqtt() throws Exception + { + k3po.finish(); + } } diff --git a/incubator/binding-asyncapi/pom.xml b/incubator/binding-asyncapi/pom.xml index eac2b62f25..5ea51f49a4 100644 --- a/incubator/binding-asyncapi/pom.xml +++ b/incubator/binding-asyncapi/pom.xml @@ -73,6 +73,12 @@ ${project.version} provided + + io.aklivity.zilla + binding-mqtt-kafka + ${project.version} + provided + io.aklivity.zilla binding-tls @@ -149,6 +155,13 @@ k3po.lang test + + io.aklivity.zilla + binding-mqtt-kafka + test-jar + ${project.version} + test + org.openjdk.jmh jmh-core diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfig.java new file mode 100644 index 0000000000..4b80d6f882 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.function.Function; + +public class AsyncapiChannelsConfig +{ + public final String sessions; + public final String messages; + public final String retained; + + public static AsyncapiChannelsConfigBuilder builder() + { + return new AsyncapiChannelsConfigBuilder<>(AsyncapiChannelsConfig.class::cast); + } + + public static AsyncapiChannelsConfigBuilder builder( + Function mapper) + { + return new AsyncapiChannelsConfigBuilder<>(mapper); + } + + public AsyncapiChannelsConfig( + String sessions, + String messages, + String retained) + { + this.sessions = sessions; + this.messages = messages; + this.retained = retained; + } +} + diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfigBuilder.java new file mode 100644 index 0000000000..815d38ab62 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfigBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +public class AsyncapiChannelsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private String sessions; + private String messages; + private String retained; + AsyncapiChannelsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + public AsyncapiChannelsConfigBuilder sessions( + String sessions) + { + this.sessions = sessions; + return this; + } + + public AsyncapiChannelsConfigBuilder messages( + String messages) + { + this.messages = messages; + return this; + } + + public AsyncapiChannelsConfigBuilder retained( + String retained) + { + this.retained = retained; + return this; + } + + + @Override + public T build() + { + return mapper.apply( + new AsyncapiChannelsConfig(sessions, messages, retained)); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java index c289b113e9..628692248a 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java @@ -18,13 +18,19 @@ public class AsyncapiConfig { + public final String apiLabel; + public final long apiId; public final String location; public final Asyncapi asyncapi; public AsyncapiConfig( + String apiLabel, + long apiId, String location, Asyncapi asyncapi) { + this.apiLabel = apiLabel; + this.apiId = apiId; this.location = location; this.asyncapi = asyncapi; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfig.java new file mode 100644 index 0000000000..a4e5190e8c --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.function.Function; + +public class AsyncapiMqttKafkaConfig +{ + public final AsyncapiChannelsConfig channels; + + public static AsyncapiMqttKafkaConfigBuilder builder() + { + return new AsyncapiMqttKafkaConfigBuilder<>(AsyncapiMqttKafkaConfig.class::cast); + } + + public static AsyncapiMqttKafkaConfigBuilder builder( + Function mapper) + { + return new AsyncapiMqttKafkaConfigBuilder<>(mapper); + } + + public AsyncapiMqttKafkaConfig( + AsyncapiChannelsConfig channels) + { + this.channels = channels; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfigBuilder.java new file mode 100644 index 0000000000..31c2566380 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfigBuilder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class AsyncapiMqttKafkaConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private AsyncapiChannelsConfig channels; + + AsyncapiMqttKafkaConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public AsyncapiMqttKafkaConfigBuilder channels( + AsyncapiChannelsConfig channels) + { + this.channels = channels; + return this; + } + + @Override + public T build() + { + return mapper.apply(new AsyncapiMqttKafkaConfig(channels)); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java index e6f6132177..105f83e5d5 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java @@ -30,6 +30,7 @@ public final class AsyncapiOptionsConfig extends OptionsConfig public final TlsOptionsConfig tls; public final HttpOptionsConfig http; public final KafkaOptionsConfig kafka; + public final AsyncapiMqttKafkaConfig mqttKafka; public static AsyncapiOptionsConfigBuilder builder() { @@ -47,12 +48,29 @@ public AsyncapiOptionsConfig( TcpOptionsConfig tcp, TlsOptionsConfig tls, HttpOptionsConfig http, - KafkaOptionsConfig kafka) + KafkaOptionsConfig kafka, + AsyncapiMqttKafkaConfig mqttKafka) { this.specs = specs; this.http = http; this.tcp = tcp; this.tls = tls; this.kafka = kafka; + this.mqttKafka = mqttKafka; + } + + public long resolveApiId( + String apiLabel) + { + long apiId = -1; + for (AsyncapiConfig c : specs) + { + if (c.apiLabel.equals(apiLabel)) + { + apiId = c.apiId; + break; + } + } + return apiId; } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java index 2d3b02b3a4..06d9a852e0 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java @@ -26,6 +26,18 @@ public final class AsyncapiOptionsConfigBuilder extends ConfigBuilder> { + private static final String DEFAULT_SESSIONS_CHANNEL = "mqttSessions"; + private static final String DEFAULT_RETAINED_CHANNEL = "mqttRetained"; + private static final String DEFAULT_MESSAGES_CHANNEL = "mqttMessages"; + private static final AsyncapiMqttKafkaConfig DEFAULT_MQTT_KAFKA = + AsyncapiMqttKafkaConfig.builder() + .channels(AsyncapiChannelsConfig.builder() + .sessions(DEFAULT_SESSIONS_CHANNEL) + .messages(DEFAULT_MESSAGES_CHANNEL) + .retained(DEFAULT_RETAINED_CHANNEL) + .build()) + .build(); + private final Function mapper; public List specs; @@ -33,6 +45,7 @@ public final class AsyncapiOptionsConfigBuilder extends ConfigBuilder mapper) @@ -82,10 +95,16 @@ public AsyncapiOptionsConfigBuilder kafka( return this; } + public AsyncapiOptionsConfigBuilder mqttKafka( + AsyncapiMqttKafkaConfig mqttKafka) + { + this.mqttKafka = mqttKafka; + return this; + } @Override public T build() { - return mapper.apply(new AsyncapiOptionsConfig(specs, tcp, tls, http, kafka)); + return mapper.apply(new AsyncapiOptionsConfig(specs, tcp, tls, http, kafka, mqttKafka)); } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java index c29cd11e35..8f4af3e8fa 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java @@ -15,6 +15,7 @@ package io.aklivity.zilla.runtime.binding.asyncapi.internal; import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; import java.util.EnumMap; @@ -34,6 +35,7 @@ public AsyncapiBindingAdapter() Map> composites = new EnumMap<>(KindConfig.class); composites.put(SERVER, new AsyncapiServerCompositeBindingAdapter()::adapt); composites.put(CLIENT, new AsyncapiClientCompositeBindingAdapter()::adapt); + composites.put(PROXY, new AsyncapiProxyCompositeBindingAdapter()::adapt); this.composites = composites; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java index 4e7b99c174..d97c23edb7 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java @@ -15,12 +15,14 @@ package io.aklivity.zilla.runtime.binding.asyncapi.internal; import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; import java.util.EnumMap; import java.util.Map; import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiClientFactory; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiProxyFactory; import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiServerFactory; import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiStreamFactory; import io.aklivity.zilla.runtime.engine.EngineContext; @@ -40,6 +42,7 @@ final class AsyncapiBindingContext implements BindingContext Map factories = new EnumMap<>(KindConfig.class); factories.put(SERVER, new AsyncapiServerFactory(config, context)); factories.put(CLIENT, new AsyncapiClientFactory(config, context)); + factories.put(PROXY, new AsyncapiProxyFactory(config, context)); this.factories = factories; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java index f725f2b98d..5926ffbab6 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java @@ -44,7 +44,6 @@ public BindingConfig adapt( this.qname = binding.qname; this.qvault = binding.qvault; this.protocol = resolveProtocol(firstServer.protocol(), options); - this.compositePorts = protocol.resolvePorts(); this.isTlsEnabled = protocol.isSecure(); return BindingConfig.builder(binding) diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java index 1c0e781af7..814564ae57 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java @@ -14,6 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -25,9 +26,8 @@ public class AsyncapiCompositeBindingAdapter protected static final String APPLICATION_JSON = "application/json"; protected Asyncapi asyncapi; + protected Map asyncApis; protected boolean isTlsEnabled; - protected int[] allPorts; - protected int[] compositePorts; protected AsyncapiProtocol protocol; protected String qname; protected String qvault; diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java index 1663d73a62..bbcc1757ec 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java @@ -121,7 +121,7 @@ public BindingConfigBuilder injectProtocolServerRoutes( @Override protected boolean isSecure() { - return scheme.equals(SECURE_SCHEME); + return findFirstServerUrlWithScheme(SECURE_SCHEME) != null; } private HttpOptionsConfigBuilder injectHttpServerOptions( diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java new file mode 100644 index 0000000000..03386813a4 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java @@ -0,0 +1,150 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiConditionConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiRouteConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; + +public class AsyncapiProxyCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final String ASYNCAPI_SEND_ACTION_NAME = "send"; + private static final String ASYNCAPI_RECEIVE_ACTION_NAME = "receive"; + private static final String ASYNCAPI_KAFKA_PROTOCOL_NAME = "kafka"; + private static final String ASYNCAPI_MQTT_PROTOCOL_NAME = "mqtt"; + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; + List routes = binding.routes.stream() + .map(r -> new AsyncapiRouteConfig(r, options::resolveApiId)) + .collect(Collectors.toList()); + this.asyncApis = options.specs.stream().collect(Collectors.toUnmodifiableMap(a -> a.apiLabel, a -> a.asyncapi)); + this.qname = binding.qname; + + String sessions = ""; + String messages = ""; + String retained = ""; + for (Asyncapi asyncapi : asyncApis.values()) + { + if (asyncapi.channels.containsKey(options.mqttKafka.channels.sessions)) + { + sessions = asyncapi.channels.get(options.mqttKafka.channels.sessions).address; + } + + if (asyncapi.channels.containsKey(options.mqttKafka.channels.messages)) + { + messages = asyncapi.channels.get(options.mqttKafka.channels.messages).address; + } + + if (asyncapi.channels.containsKey(options.mqttKafka.channels.retained)) + { + retained = asyncapi.channels.get(options.mqttKafka.channels.retained).address; + } + } + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/%s", qname, "mqtt-kafka")) + .binding() + .name("mqtt_kafka_proxy0") + .type("mqtt-kafka") + .kind(PROXY) + .options(MqttKafkaOptionsConfig::builder) + .topics() + .sessions(sessions) + .messages(messages) + .retained(retained) + .build() + .clients(Collections.emptyList()) + .build() + .inject(b -> this.injectMqttKafkaRoutes(b, routes)) + .build() + .build() + .build(); + } + + public BindingConfigBuilder injectMqttKafkaRoutes( + BindingConfigBuilder binding, + List routes) + { + inject: + for (AsyncapiRouteConfig route : routes) + { + final RouteConfigBuilder> routeBuilder = binding.route(); + + final Asyncapi kafkaAsyncapi = asyncApis.get(route.with.apiId); + + if (kafkaAsyncapi.servers.values().stream().anyMatch(s -> !s.protocol.startsWith(ASYNCAPI_KAFKA_PROTOCOL_NAME))) + { + break inject; + } + + final AsyncapiOperation withOperation = kafkaAsyncapi.operations.get(route.with.operationId); + final String messages = AsyncapiChannelView.of(kafkaAsyncapi.channels, withOperation.channel).address(); + + for (AsyncapiConditionConfig condition : route.when) + { + final Asyncapi mqttAsyncapi = asyncApis.get(condition.apiId); + if (mqttAsyncapi.servers.values().stream().anyMatch(s -> !s.protocol.startsWith(ASYNCAPI_MQTT_PROTOCOL_NAME))) + { + break inject; + } + + final AsyncapiOperation whenOperation = mqttAsyncapi.operations.get(condition.operationId); + final AsyncapiChannelView channel = AsyncapiChannelView.of(mqttAsyncapi.channels, whenOperation.channel); + final MqttKafkaConditionKind kind = whenOperation.action.equals(ASYNCAPI_SEND_ACTION_NAME) ? + MqttKafkaConditionKind.PUBLISH : MqttKafkaConditionKind.SUBSCRIBE; + final String topic = channel.address().replaceAll("\\{[^}]+\\}", "+"); + routeBuilder + .when(MqttKafkaConditionConfig::builder) + .topic(topic) + .kind(kind) + .build() + .with(MqttKafkaWithConfig::builder) + .messages(messages) + .build() + .exit(qname); + } + binding = routeBuilder.build(); + } + return binding; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java index 970054fc70..7559286e09 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java @@ -31,8 +31,6 @@ public class AsyncapiServerCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi { private int[] compositePorts; - private int[] compositeSecurePorts; - private boolean isPlainEnabled; @Override public String type() diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java index 1b54d2d687..1e2e27860e 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java @@ -14,8 +14,8 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal; -import java.net.URI; import java.util.Map; +import java.util.regex.Pattern; import java.util.stream.Collectors; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; @@ -24,7 +24,6 @@ import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; -import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfigBuilder; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; @@ -42,6 +41,8 @@ public class AyncapiKafkaProtocol extends AsyncapiProtocol private static final String SCHEME = "kafka"; private static final String SECURE_SCHEME = ""; private static final String SECURE_PROTOCOL = "kafka-secure"; + private static final Pattern PARAMETERIZED_TOPIC_PATTERN = Pattern.compile("\\{.*?\\}"); + private final String protocol; private final KafkaSaslConfig sasl; @@ -65,12 +66,19 @@ public NamespaceConfigBuilder injectProtocolClientCache( .name("kafka_cache_client0") .type("kafka") .kind(KindConfig.CACHE_CLIENT) + .options(KafkaOptionsConfig::builder) + .inject(this::injectKafkaTopicOptions) + .build() .exit("kafka_cache_server0") .build() .binding() .name("kafka_cache_server0") .type("kafka") .kind(KindConfig.CACHE_SERVER) + .options(KafkaOptionsConfig::builder) + .inject(this::injectKafkaBootstrapOptions) + .inject(this::injectKafkaTopicOptions) + .build() .exit("kafka_client0") .build(); } @@ -79,16 +87,9 @@ public NamespaceConfigBuilder injectProtocolClientCache( public BindingConfigBuilder injectProtocolClientOptions( BindingConfigBuilder binding) { - return sasl == null ? binding : - binding.options(KafkaOptionsConfig::builder) - .sasl(KafkaSaslConfig::builder) - .mechanism(sasl.mechanism) - .username(sasl.username) - .password(sasl.password) - .build() + return binding.options(KafkaOptionsConfig::builder) + .inject(this::injectKafkaSaslOptions) .inject(this::injectKafkaServerOptions) - //.inject(this::injectKafkaBootstrapOptions) - .inject(this::injectKafkaTopicOptions) .build(); } @@ -112,15 +113,25 @@ protected boolean isSecure() return protocol.equals(SECURE_PROTOCOL); } + private KafkaOptionsConfigBuilder injectKafkaSaslOptions( + KafkaOptionsConfigBuilder options) + { + return sasl != null ? options.sasl(KafkaSaslConfig::builder) + .mechanism(sasl.mechanism) + .username(sasl.username) + .password(sasl.password) + .build() : options; + } + private KafkaOptionsConfigBuilder injectKafkaServerOptions( KafkaOptionsConfigBuilder options) { return options.servers(asyncApi.servers.values().stream().map(s -> { - final URI serverUrl = AsyncapiServerView.of(s).url(); + String[] hostAndPort = s.host.split(":"); return KafkaServerConfig.builder() - .host(serverUrl.getHost()) - .port(serverUrl.getPort()) + .host(hostAndPort[0]) + .port(Integer.parseInt(hostAndPort[1])) .build(); }).collect(Collectors.toList())); } @@ -148,6 +159,14 @@ private KafkaOptionsConfigBuilder injectKafkaTopicOptions( return options; } + private KafkaOptionsConfigBuilder injectKafkaBootstrapOptions( + KafkaOptionsConfigBuilder options) + { + return options.bootstrap(asyncApi.channels.values().stream() + .filter(c -> !PARAMETERIZED_TOPIC_PATTERN.matcher(c.address).find()) + .map(c -> AsyncapiChannelView.of(asyncApi.channels, c).address()).collect(Collectors.toList())); + } + private KafkaTopicConfigBuilder injectValue( KafkaTopicConfigBuilder topic, Map messages) diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java index 685544ab0e..cf315bc69c 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java @@ -94,6 +94,6 @@ public BindingConfigBuilder injectProtocolServerRoutes( @Override protected boolean isSecure() { - return scheme.equals(SECURE_SCHEME); + return findFirstServerUrlWithScheme(SECURE_SCHEME) != null; } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java index 02564005b2..cf7c05c1d3 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java @@ -52,7 +52,7 @@ public final class AsyncapiBindingConfig public final List routes; private final Int2ObjectHashMap composites; private final long overrideRouteId; - private final Long2LongHashMap resolvedIds; + private final Long2LongHashMap compositeResolvedIds; private final HttpHeaderHelper helper; private final Object2ObjectHashMap paths; private final Map operationIds; @@ -66,14 +66,15 @@ public AsyncapiBindingConfig( this.kind = binding.kind; this.overrideRouteId = overrideRouteId; this.options = AsyncapiOptionsConfig.class.cast(binding.options); - this.routes = binding.routes.stream().map(AsyncapiRouteConfig::new).collect(toList()); - this.resolvedIds = binding.composites.stream() + this.routes = binding.routes.stream().map(r -> new AsyncapiRouteConfig(r, options::resolveApiId)).collect(toList()); + this.compositeResolvedIds = binding.composites.stream() .map(c -> c.bindings) .flatMap(List::stream) - .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka") && b.kind == CACHE_CLIENT) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || + b.type.equals("kafka") && b.kind == CACHE_CLIENT || b.type.equals("mqtt-kafka")) .collect(of( () -> new Long2LongHashMap(-1), - (m, r) -> m.put(0L, r.id), //TODO: populate proper apiId + (m, r) -> m.put(options.specs.get(0).apiId, r.id), (m, r) -> m, IDENTITY_FINISH )); @@ -81,7 +82,8 @@ public AsyncapiBindingConfig( binding.composites.stream() .map(c -> c.bindings) .flatMap(List::stream) - .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka") && b.kind == CACHE_CLIENT) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka") && b.kind == CACHE_CLIENT || + b.type.equals("mqtt-kafka")) .forEach(b -> this.composites.put(NamespacedId.namespaceId(b.id), b.type)); this.paths = new Object2ObjectHashMap<>(); @@ -116,10 +118,10 @@ public String getCompositeOriginType( return composites.get(NamespacedId.namespaceId(originId)); } - public long resolveResolvedId( + public long resolveCompositeResolvedId( long apiId) { - return overrideRouteId != -1 ? overrideRouteId : resolvedIds.get(apiId); + return overrideRouteId != -1 ? overrideRouteId : compositeResolvedIds.get(apiId); } public String resolveOperationId( @@ -153,6 +155,16 @@ public AsyncapiRouteConfig resolve( .orElse(null); } + public AsyncapiRouteConfig resolve( + long authorization, + long apiId) + { + return routes.stream() + .filter(r -> r.authorized(authorization) && r.matches(apiId)) + .findFirst() + .orElse(null); + } + private static final class HttpHeaderHelper { private static final String8FW HEADER_NAME_METHOD = new String8FW(":method"); diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfig.java new file mode 100644 index 0000000000..b3e3f70c62 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; + +public class AsyncapiConditionConfig extends ConditionConfig +{ + public final String apiId; + public final String operationId; + + public AsyncapiConditionConfig( + String apiId, + String operationId) + { + this.apiId = apiId; + this.operationId = operationId; + } + + public boolean matches( + long apiId, + Function supplyApiId) + { + return matchesApiId(apiId, supplyApiId); + } + + private boolean matchesApiId( + long apiId, + Function supplyApiId) + { + return supplyApiId.apply(this.apiId) == apiId; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapter.java new file mode 100644 index 0000000000..8d659d7141 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; +import io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi; + +public class AsyncapiConditionConfigAdapter implements ConditionConfigAdapterSpi, JsonbAdapter +{ + private static final String API_ID_NAME = "api-id"; + private static final String OPERATION_ID_NAME = "operation-id"; + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + ConditionConfig condition) + { + AsyncapiConditionConfig asyncapiCondition = (AsyncapiConditionConfig) condition; + JsonObjectBuilder object = Json.createObjectBuilder(); + + object.add(API_ID_NAME, asyncapiCondition.apiId); + object.add(OPERATION_ID_NAME, asyncapiCondition.operationId); + + return object.build(); + } + + @Override + public ConditionConfig adaptFromJson( + JsonObject object) + { + String apiId = object.getString(API_ID_NAME); + String operationId = object.getString(OPERATION_ID_NAME); + return new AsyncapiConditionConfig(apiId, operationId); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java index 2d24c4bf54..1b55629f9c 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java @@ -20,13 +20,14 @@ import java.io.InputStream; import java.io.Reader; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.function.Function; +import java.util.zip.CRC32C; import jakarta.json.Json; -import jakarta.json.JsonArray; -import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonReader; @@ -41,7 +42,11 @@ import org.leadpony.justify.api.JsonValidationService; import org.leadpony.justify.api.ProblemHandler; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiChannelsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiChannelsConfigBuilder; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiMqttKafkaConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiMqttKafkaConfigBuilder; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfigBuilder; import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; @@ -63,7 +68,13 @@ public final class AsyncapiOptionsConfigAdapter implements OptionsConfigAdapterS private static final String TLS_NAME = "tls"; private static final String HTTP_NAME = "http"; private static final String KAFKA_NAME = "kafka"; + private static final String MQTT_KAFKA_NAME = "mqtt_kafka"; + private static final String CHANNELS_NAME = "channels"; + private static final String SESSIONS_NAME = "sessions"; + private static final String MESSAGES_NAME = "messages"; + private static final String RETAINED_NAME = "retained"; + private CRC32C crc; private OptionsConfigAdapter tcpOptions; private OptionsConfigAdapter tlsOptions; private OptionsConfigAdapter httpOptions; @@ -91,9 +102,9 @@ public JsonObject adaptToJson( if (asyncapiOptions.specs != null) { - JsonArrayBuilder keys = Json.createArrayBuilder(); - asyncapiOptions.specs.forEach(p -> keys.add(p.location)); - object.add(SPECS_NAME, keys); + JsonObjectBuilder specs = Json.createObjectBuilder(); + asyncapiOptions.specs.forEach(p -> specs.add(p.apiLabel, p.location)); + object.add(SPECS_NAME, specs); } if (asyncapiOptions.tcp != null) @@ -120,6 +131,37 @@ public JsonObject adaptToJson( object.add(KAFKA_NAME, kafkaOptions.adaptToJson(kafka)); } + if (asyncapiOptions.mqttKafka != null) + { + AsyncapiMqttKafkaConfig mqttKafka = asyncapiOptions.mqttKafka; + JsonObjectBuilder newMqttKafka = Json.createObjectBuilder(); + AsyncapiChannelsConfig channels = mqttKafka.channels; + if (channels != null) + { + JsonObjectBuilder newChannels = Json.createObjectBuilder(); + String sessions = channels.sessions; + if (sessions != null) + { + newChannels.add(SESSIONS_NAME, sessions); + } + + String messages = channels.messages; + if (messages != null) + { + newChannels.add(MESSAGES_NAME, messages); + } + + String retained = channels.retained; + if (retained != null) + { + newChannels.add(RETAINED_NAME, retained); + } + newMqttKafka.add(CHANNELS_NAME, newChannels); + + object.add(MQTT_KAFKA_NAME, newMqttKafka); + } + } + return object.build(); } @@ -130,7 +172,7 @@ public OptionsConfig adaptFromJson( final AsyncapiOptionsConfigBuilder asyncapiOptions = AsyncapiOptionsConfig.builder(); List specs = object.containsKey(SPECS_NAME) - ? asListAsyncapis(object.getJsonArray(SPECS_NAME)) + ? asListAsyncapis(object.getJsonObject(SPECS_NAME)) : null; asyncapiOptions.specs(specs); @@ -162,6 +204,30 @@ public OptionsConfig adaptFromJson( asyncapiOptions.kafka(kafkaOptions); } + if (object.containsKey(MQTT_KAFKA_NAME)) + { + AsyncapiMqttKafkaConfigBuilder mqttKafkaBuilder = AsyncapiMqttKafkaConfig.builder(); + final JsonObject mqttKafka = object.getJsonObject(MQTT_KAFKA_NAME); + if (mqttKafka.containsKey(CHANNELS_NAME)) + { + AsyncapiChannelsConfigBuilder channelsBuilder = AsyncapiChannelsConfig.builder(); + JsonObject channels = mqttKafka.getJsonObject(CHANNELS_NAME); + + if (channels.containsKey(SESSIONS_NAME)) + { + channelsBuilder.sessions(channels.getString(SESSIONS_NAME)); + } + if (channels.containsKey(MESSAGES_NAME)) + { + channelsBuilder.messages(channels.getString(MESSAGES_NAME)); + } + if (channels.containsKey(RETAINED_NAME)) + { + channelsBuilder.retained(channels.getString(RETAINED_NAME)); + } + asyncapiOptions.mqttKafka(mqttKafkaBuilder.channels(channelsBuilder.build()).build()); + } + } return asyncapiOptions.build(); } @@ -178,24 +244,29 @@ public void adaptContext( this.httpOptions.adaptType("http"); this.kafkaOptions = new OptionsConfigAdapter(Kind.BINDING, context); this.kafkaOptions.adaptType("kafka"); + this.crc = new CRC32C(); } private List asListAsyncapis( - JsonArray array) + JsonObject array) { - return array.stream() + return array.entrySet().stream() .map(this::asAsyncapi) .collect(toList()); } private AsyncapiConfig asAsyncapi( - JsonValue value) + Map.Entry entry) { - final String location = ((JsonString) value).getString(); + final String apiLabel = entry.getKey(); + final String location = ((JsonString) entry.getValue()).getString(); final String specText = readURL.apply(location); + crc.reset(); + crc.update(specText.getBytes(StandardCharsets.UTF_8)); + final long apiId = crc.getValue(); Asyncapi asyncapi = parseAsyncapi(specText); - return new AsyncapiConfig(location, asyncapi); + return new AsyncapiConfig(apiLabel, apiId, location, asyncapi); } private Asyncapi parseAsyncapi( diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java index b1b54aa41d..00545968fe 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java @@ -14,7 +14,10 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; +import static java.util.stream.Collectors.toList; +import java.util.List; +import java.util.function.Function; import java.util.function.LongPredicate; import io.aklivity.zilla.runtime.engine.config.RouteConfig; @@ -22,14 +25,22 @@ public final class AsyncapiRouteConfig { public final long id; - + public final AsyncapiWithConfig with; + public final List when; private final LongPredicate authorized; + private final Function supplyApiId; public AsyncapiRouteConfig( - RouteConfig route) + RouteConfig route, + Function supplyApiId) { this.id = route.id; this.authorized = route.authorized; + this.when = route.when.stream() + .map(AsyncapiConditionConfig.class::cast) + .collect(toList()); + this.with = (AsyncapiWithConfig) route.with; + this.supplyApiId = supplyApiId; } boolean authorized( @@ -37,4 +48,10 @@ boolean authorized( { return authorized.test(authorization); } + + boolean matches( + long apiId) + { + return when.isEmpty() || when.stream().anyMatch(m -> m.matches(apiId, supplyApiId)); + } } diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfig.java similarity index 66% rename from runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfig.java rename to incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfig.java index 5d7b0b77d7..285af68e8e 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfig.java @@ -12,17 +12,20 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; import io.aklivity.zilla.runtime.engine.config.WithConfig; -public class MqttKafkaWithConfig extends WithConfig +public class AsyncapiWithConfig extends WithConfig { - public final String messages; + public final String apiId; + public final String operationId; - public MqttKafkaWithConfig( - String messages) + public AsyncapiWithConfig( + String apiId, + String operationId) { - this.messages = messages; + this.apiId = apiId; + this.operationId = operationId; } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapter.java new file mode 100644 index 0000000000..7fa0347114 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.engine.config.WithConfig; +import io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi; + +public class AsyncapiWithConfigAdapter implements WithConfigAdapterSpi, JsonbAdapter +{ + private static final String API_ID_NAME = "api-id"; + private static final String OPERATION_ID_NAME = "operation-id"; + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + WithConfig with) + { + AsyncapiWithConfig config = (AsyncapiWithConfig) with; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (config.apiId != null) + { + object.add(API_ID_NAME, config.apiId); + } + + if (config.operationId != null) + { + object.add(OPERATION_ID_NAME, config.operationId); + } + + return object.build(); + } + + @Override + public WithConfig adaptFromJson( + JsonObject object) + { + String apiId = object.containsKey(API_ID_NAME) + ? object.getString(API_ID_NAME) + : null; + + String operationId = object.containsKey(OPERATION_ID_NAME) + ? object.getString(OPERATION_ID_NAME) + : null; + + return new AsyncapiWithConfig(apiId, operationId); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java index 4f79c24260..1c53959db8 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java @@ -20,4 +20,5 @@ public class AsyncapiOperation { public Map bindings; public AsyncapiChannel channel; + public String action; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java index bc448d830a..3a4e590864 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java @@ -149,7 +149,7 @@ public MessageConsumer newStream( final long apiId = asyncapiBeginEx.apiId(); final String operationId = asyncapiBeginEx.operationId().asString(); - final long resolvedId = binding.resolveResolvedId(apiId); + final long resolvedId = binding.resolveCompositeResolvedId(apiId); if (resolvedId != -1) { diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiProxyFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiProxyFactory.java new file mode 100644 index 0000000000..bd90cbe7a3 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiProxyFactory.java @@ -0,0 +1,1764 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfiguration; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiBindingConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiRouteConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class AsyncapiProxyFactory implements AsyncapiStreamFactory +{ + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); + + private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); + + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Function supplyTypeId; + private final Long2ObjectHashMap bindings; + private final Long2LongHashMap apiIds; + private final int asyncapiTypeId; + + private final AsyncapiConfiguration config; + + public AsyncapiProxyFactory( + AsyncapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTypeId = context::supplyTypeId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.apiIds = new Long2LongHashMap(-1); + this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); + } + + + @Override + public int routedTypeId() + { + return asyncapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + AsyncapiBindingConfig asyncapiBinding = new AsyncapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, asyncapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + + final AsyncapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + if (!binding.isCompositeOriginId(originId)) + { + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + final long apiId = asyncapiBeginEx.apiId(); + final String operationId = asyncapiBeginEx.operationId().asString(); + + final long compositeResolvedId = binding.resolveCompositeResolvedId(apiId); + apiIds.put(apiId, apiId); + + if (compositeResolvedId != -1) + { + newStream = new AsyncapiServerStream( + receiver, + originId, + routedId, + initialId, + apiId, + authorization, + compositeResolvedId, + operationId)::onAsyncapiServerMessage; + } + } + else + { + final long apiId = apiIds.get(affinity); + final AsyncapiRouteConfig route = binding.resolve(authorization, apiId); + + if (route != null) + { + final long clientApiId = binding.options.resolveApiId(route.with.apiId); + newStream = new CompositeClientStream( + receiver, + originId, + routedId, + initialId, + authorization, + route.id, + affinity, + clientApiId)::onCompositeClientMessage; + } + else + { + // Needed by the willStream which comes directly from the composite without Asyncapi begin pair + Optional asyncapiRoute = binding.routes.stream().findFirst(); + final long routeId = asyncapiRoute.map(r -> r.id).orElse(0L); + final long clientApiId = asyncapiRoute.map(asyncapiRouteConfig -> + binding.options.resolveApiId(asyncapiRouteConfig.with.apiId)).orElse(0L); + + newStream = new CompositeClientStream( + receiver, + originId, + routedId, + initialId, + authorization, + routeId, + affinity, + clientApiId)::onCompositeClientMessage; + } + } + } + + return newStream; + } + + private final class AsyncapiServerStream + { + private final CompositeServerStream delegate; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private AsyncapiServerStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long compositeResolvedId, + String operationId) + { + this.delegate = + new CompositeServerStream(this, compositeResolvedId, compositeResolvedId, authorization, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onAsyncapiServerMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiServerBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiServerData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiServerEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiServerFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiServerAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiServerWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiServerReset(reset); + break; + default: + break; + } + } + + private void onAsyncapiServerBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = AsyncapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + delegate.doCompositeBegin(traceId, affinity, asyncapiBeginEx.extension()); + } + + private void onAsyncapiServerData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onAsyncapiServerEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiServerFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiServerAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeAbort(traceId, extension); + } + + private void doAsyncapiServerReset( + long traceId) + { + if (!AsyncapiState.initialClosed(state)) + { + state = AsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doAsyncapiServerWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = delegate.initialAck; + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void doAsyncapiServerBegin( + long traceId, + OctetsFW extension) + { + state = AsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .extension(extension) + .build(); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, asyncapiBeginEx); + } + + private void doAsyncapiServerData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doAsyncapiServerFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, reserved, extension); + } + + private void doAsyncapiServerEnd( + long traceId, + OctetsFW extension) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = AsyncapiState.closeReply(state); + } + + private void doAsyncapiServerAbort( + long traceId) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = AsyncapiState.closeInitial(state); + } + + private void onAsyncapiServerReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = AsyncapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onAsyncapiServerWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeWindow(traceId, acknowledge, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doAsyncapiServerReset(traceId); + doAsyncapiServerAbort(traceId); + + delegate.cleanup(traceId); + } + } + + final class CompositeServerStream + { + private final String operationId; + private final long originId; + private final long routedId; + private final long authorization; + + private AsyncapiServerStream delegate; + private long initialId; + private long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeServerStream( + AsyncapiServerStream delegate, + long routedId, + long compositeResolvedId, + long authorization, + String operationId) + { + this.delegate = delegate; + this.originId = routedId; + this.routedId = compositeResolvedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onCompositeServerMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = AsyncapiState.openingReply(state); + + delegate.doAsyncapiServerBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final int flags = data.flags(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doAsyncapiServerData(traceId, flags, reserved, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doAsyncapiServerFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiServerEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiServerAbort(traceId); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = AsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doAsyncapiServerReset(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = AsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiServerWindow(authorization, traceId, budgetId, padding); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = AsyncapiState.closeReply(state); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doCompositeBegin( + long traceId, + long affinity, + OctetsFW extension) + { + if (!AsyncapiState.initialOpening(state)) + { + assert state == 0; + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.receiver = newStream(this::onCompositeServerMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, affinity, extension); + state = AsyncapiState.openingInitial(state); + } + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + final class CompositeClientStream + { + private final long originId; + private final long routedId; + private final MessageConsumer sender; + private final long affinity; + private final long authorization; + private final AsyncapiClientStream delegate; + + private long initialId; + private long replyId; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeClientStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long authorization, + long resolvedId, + long affinity, + long apiId) + { + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.delegate = new AsyncapiClientStream(this, originId, resolvedId, authorization, apiId); + } + + private void onCompositeClientMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + + state = AsyncapiState.openingInitial(state); + + delegate.doAsyncapiClientBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final int flags = data.flags(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiClientData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiClientFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiClientEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiClientAbort(traceId, EMPTY_OCTETS); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + + replyAck = acknowledge; + + state = AsyncapiState.closeReply(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doAsyncapiClientReset(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyPad = padding; + state = AsyncapiState.openReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiClientWindow(authorization, traceId, budgetId, padding); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.initialClosed(state)) + { + state = AsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + initialAck = Math.max(delegate.initialAck, 0); + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + replySeq = delegate.replySeq; + replyAck = delegate.replyAck; + replyMax = delegate.replyMax; + state = AsyncapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + replySeq += reserved; + + assert replySeq <= replyAck + replyMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.replyClosed(state)) + { + replySeq = delegate.replySeq; + state = AsyncapiState.closeReply(state); + + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, traceId, authorization, extension); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.replyClosed(state)) + { + replySeq = delegate.replySeq; + state = AsyncapiState.closeReply(state); + + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + final class AsyncapiClientStream + { + private final long originId; + private final long routedId; + private final long authorization; + private final CompositeClientStream delegate; + private final long apiId; + + private long initialId; + private long replyId; + private MessageConsumer asyncapi; + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private AsyncapiClientStream( + CompositeClientStream delegate, + long originId, + long routedId, + long authorization, + long apiId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.authorization = authorization; + this.apiId = apiId; + } + + private void onAsyncapiClientMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiClientBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiClientData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiClientFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiClientEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiClientAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiClientReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiClientWindow(window); + break; + default: + break; + } + } + + private void onAsyncapiClientBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = AsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + final OctetsFW asyncapiExtension = asyncapiBeginEx != null ? asyncapiBeginEx.extension() : EMPTY_OCTETS; + + delegate.doCompositeBegin(traceId, asyncapiExtension); + } + + private void onAsyncapiClientData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onAsyncapiClientFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiClientEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiClientAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeAbort(traceId, EMPTY_OCTETS); + } + + private void onAsyncapiClientReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = AsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doCompositeReset(traceId); + } + + + private void onAsyncapiClientWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = AsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeWindow(authorization, traceId, budgetId, padding); + } + + private void doAsyncapiClientReset( + long traceId) + { + if (!AsyncapiState.replyClosed(state)) + { + doReset(asyncapi, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = AsyncapiState.closeReply(state); + } + } + + private void doAsyncapiClientWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(asyncapi, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doAsyncapiClientBegin( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialOpening(state)) + { + assert state == 0; + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .apiId(apiId) + .operationId((String) null) + .extension(extension) + .build(); + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.asyncapi = newStream(this::onAsyncapiClientMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, 0L, asyncapiBeginEx); + state = AsyncapiState.openingInitial(state); + } + } + + private void doAsyncapiClientData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doAsyncapiClientFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doAsyncapiClientEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doEnd(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void doAsyncapiClientAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doAbort(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doAsyncapiClientAbort(traceId, EMPTY_OCTETS); + delegate.doCompositeReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java index cbe69b5e6a..708507f8f4 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java @@ -164,6 +164,7 @@ public MessageConsumer newStream( final String operationId = compositeTypeId == httpTypeId ? binding.resolveOperationId(extension.get(httpBeginRO::tryWrap)) : null; + final long apiId = binding.options.specs.get(0).apiId; newStream = new CompositeStream( receiver, originId, @@ -172,6 +173,7 @@ public MessageConsumer newStream( affinity, authorization, route.id, + apiId, operationId)::onCompositeMessage; } } @@ -211,9 +213,10 @@ private CompositeStream( long affinity, long authorization, long resolvedId, + long apiId, String operationId) { - this.delegate = new AsyncapiStream(this, routedId, resolvedId, authorization, operationId); + this.delegate = new AsyncapiStream(this, routedId, resolvedId, authorization, apiId, operationId); this.sender = sender; this.originId = originId; this.routedId = routedId; @@ -514,6 +517,7 @@ final class AsyncapiStream { private final CompositeStream delegate; private final String operationId; + private final long apiId; private final long originId; private final long routedId; private final long authorization; @@ -539,6 +543,7 @@ private AsyncapiStream( long originId, long routedId, long authorization, + long apiId, String operationId) { this.delegate = delegate; @@ -546,6 +551,7 @@ private AsyncapiStream( this.routedId = routedId; this.receiver = MessageConsumer.NOOP; this.authorization = authorization; + this.apiId = apiId; this.operationId = operationId; } @@ -764,6 +770,7 @@ private void doAsyncapiBegin( final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW .wrap(extBuffer, 0, extBuffer.capacity()) .typeId(asyncapiTypeId) + .apiId(apiId) .operationId(operationId) .extension(extension) .build(); diff --git a/incubator/binding-asyncapi/src/main/moditect/module-info.java b/incubator/binding-asyncapi/src/main/moditect/module-info.java index 6c2056298b..380da1403e 100644 --- a/incubator/binding-asyncapi/src/main/moditect/module-info.java +++ b/incubator/binding-asyncapi/src/main/moditect/module-info.java @@ -14,10 +14,13 @@ */ module io.aklivity.zilla.runtime.binding.asyncapi { + requires com.fasterxml.jackson.core; + requires com.fasterxml.jackson.databind; requires io.aklivity.zilla.runtime.engine; requires io.aklivity.zilla.runtime.binding.mqtt; requires io.aklivity.zilla.runtime.binding.http; requires io.aklivity.zilla.runtime.binding.kafka; + requires io.aklivity.zilla.runtime.binding.mqtt.kafka; requires io.aklivity.zilla.runtime.binding.tcp; requires io.aklivity.zilla.runtime.binding.tls; requires io.aklivity.zilla.runtime.catalog.inline; @@ -25,6 +28,7 @@ requires io.aklivity.zilla.runtime.vault.filesystem; requires io.aklivity.zilla.runtime.model.core; requires io.aklivity.zilla.runtime.model.json; + requires org.leadpony.justify; opens io.aklivity.zilla.runtime.binding.asyncapi.internal.model; @@ -36,6 +40,12 @@ provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi with io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiOptionsConfigAdapter; + provides io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiConditionConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiWithConfigAdapter; + provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi with io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingAdapter; } diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi new file mode 100644 index 0000000000..34cdfb042f --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiConditionConfigAdapter diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi new file mode 100644 index 0000000000..5eb2c445a3 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiWithConfigAdapter diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java index cc1e44bff7..29ff6df714 100644 --- a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java @@ -72,9 +72,9 @@ public void shouldAddCompositeBinding() String text = "{" + "\"specs\":" + - "[" + - "\"mqtt/asyncapi.yaml\"" + - "]" + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"" + + "}" + "}"; AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapterTest.java new file mode 100644 index 0000000000..c3257a3871 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class AsyncapiConditionConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new AsyncapiConditionConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadCondition() + { + String text = + "{" + + "\"api-id\":\"test\"," + + "\"operation-id\":\"testOperationId\"" + + "}"; + + AsyncapiConditionConfig condition = jsonb.fromJson(text, AsyncapiConditionConfig.class); + + assertThat(condition, not(nullValue())); + assertThat(condition.apiId, equalTo("test")); + assertThat(condition.operationId, equalTo("testOperationId")); + } + + @Test + public void shouldWriteCondition() + { + AsyncapiConditionConfig condition = new AsyncapiConditionConfig("test", "testOperationId"); + + String text = jsonb.toJson(condition); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"api-id\":\"test\"," + + "\"operation-id\":\"testOperationId\"" + + "}")); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java index 1f73927d8b..ef12eda389 100644 --- a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java @@ -39,7 +39,9 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiChannelsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiMqttKafkaConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; @@ -83,9 +85,9 @@ public void shouldReadOptionsMqtt() throws IOException String text = "{" + "\"specs\":" + - "[" + - "\"mqtt/asyncapi.yaml\"," + - "]," + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"," + + "}," + "\"tcp\":" + "{" + "\"host\":\"localhost\"," + @@ -110,15 +112,6 @@ public void shouldReadOptionsMqtt() throws IOException "[" + "\"mqtt\"" + "]" + - "}," + - "\"kafka\":" + - "{" + - "\"sasl\":" + - "{" + - "\"mechanism\":\"plain\"," + - "\"username\":\"username\"," + - "\"password\":\"password\"" + - "}" + "}" + "}"; @@ -135,9 +128,6 @@ public void shouldReadOptionsMqtt() throws IOException assertThat(options.tls.trustcacerts, equalTo(true)); assertThat(options.tls.sni, equalTo(asList("mqtt.example.net"))); assertThat(options.tls.alpn, equalTo(asList("mqtt"))); - assertThat(options.kafka.sasl.mechanism, equalTo("plain")); - assertThat(options.kafka.sasl.username, equalTo("username")); - assertThat(options.kafka.sasl.password, equalTo("password")); } @Test @@ -145,7 +135,7 @@ public void shouldWriteOptionsMqtt() throws IOException { initJson("mqtt/asyncapi.yaml"); List specs = new ArrayList<>(); - specs.add(new AsyncapiConfig("mqtt/asyncapi.yaml", new Asyncapi())); + specs.add(new AsyncapiConfig("mqtt-api", 1, "mqtt/asyncapi.yaml", new Asyncapi())); AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() @@ -177,9 +167,9 @@ public void shouldWriteOptionsMqtt() throws IOException assertThat(text, equalTo( "{" + "\"specs\":" + - "[" + - "\"mqtt/asyncapi.yaml\"" + - "]," + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"" + + "}," + "\"tcp\":" + "{" + "\"host\":\"localhost\"," + @@ -213,7 +203,16 @@ public void shouldWriteOptionsMqtt() throws IOException "\"username\":\"username\"," + "\"password\":\"password\"" + "}" + - "}" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"mqttSessions\"," + + "\"messages\":\"mqttMessages\"," + + "\"retained\":\"mqttRetained\"" + + "}" + + "}" + "}")); } @@ -224,9 +223,9 @@ public void shouldReadOptionsKafka() throws IOException String text = "{" + "\"specs\":" + - "[" + - "\"kafka/asyncapi.yaml\"," + - "]," + + "{" + + "\"kafka-api\":\"kafka/asyncapi.yaml\"," + + "}," + "\"tcp\":" + "{" + "\"host\":\"localhost\"," + @@ -286,7 +285,7 @@ public void shouldWriteOptionsHttp() throws IOException { initJson("http/asyncapi.yaml"); List specs = new ArrayList<>(); - specs.add(new AsyncapiConfig("http/asyncapi.yaml", new Asyncapi())); + specs.add(new AsyncapiConfig("http-api", 1, "http/asyncapi.yaml", new Asyncapi())); AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() @@ -311,9 +310,9 @@ public void shouldWriteOptionsHttp() throws IOException assertThat(text, equalTo( "{" + "\"specs\":" + - "[" + - "\"http/asyncapi.yaml\"" + - "]," + + "{" + + "\"http-api\":\"http/asyncapi.yaml\"" + + "}," + "\"tcp\":" + "{" + "\"host\":\"localhost\"," + @@ -338,6 +337,15 @@ public void shouldWriteOptionsHttp() throws IOException "[" + "\"http\"" + "]" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"mqttSessions\"," + + "\"messages\":\"mqttMessages\"," + + "\"retained\":\"mqttRetained\"" + + "}" + "}" + "}")); } @@ -349,9 +357,9 @@ public void shouldReadOptionsHttp() throws IOException String text = "{" + "\"specs\":" + - "[" + - "\"http/asyncapi.yaml\"," + - "]," + + "{" + + "\"http-api\":\"http/asyncapi.yaml\"," + + "}," + "\"tcp\":" + "{" + "\"host\":\"localhost\"," + @@ -399,7 +407,7 @@ public void shouldWriteOptionsKafka() throws IOException { initJson("kafka/asyncapi.yaml"); List specs = new ArrayList<>(); - specs.add(new AsyncapiConfig("kafka/asyncapi.yaml", new Asyncapi())); + specs.add(new AsyncapiConfig("kafka-api", 1, "kafka/asyncapi.yaml", new Asyncapi())); AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() @@ -431,9 +439,9 @@ public void shouldWriteOptionsKafka() throws IOException assertThat(text, equalTo( "{" + "\"specs\":" + - "[" + - "\"kafka/asyncapi.yaml\"" + - "]," + + "{" + + "\"kafka-api\":\"kafka/asyncapi.yaml\"" + + "}," + "\"tcp\":" + "{" + "\"host\":\"localhost\"," + @@ -467,7 +475,88 @@ public void shouldWriteOptionsKafka() throws IOException "\"username\":\"username\"," + "\"password\":\"password\"" + "}" + - "}" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"mqttSessions\"," + + "\"messages\":\"mqttMessages\"," + + "\"retained\":\"mqttRetained\"" + + "}" + + "}" + "}")); } + + @Test + public void shouldReadOptionsMqttKafka() throws IOException + { + initJson("mqtt/asyncapi.yaml"); + String text = + "{" + + "\"specs\":" + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"sessionsChannel\"," + + "\"messages\":\"messagesChannel\"," + + "\"retained\":\"retainedChannel\"" + + "}" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + assertThat(options, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.get(0); + assertThat(asyncapi.location, equalTo("mqtt/asyncapi.yaml")); + assertThat(asyncapi.asyncapi, instanceOf(Asyncapi.class)); + assertThat(options.mqttKafka.channels.sessions, equalTo("sessionsChannel")); + assertThat(options.mqttKafka.channels.messages, equalTo("messagesChannel")); + assertThat(options.mqttKafka.channels.retained, equalTo("retainedChannel")); + } + + @Test + public void shouldWriteOptionsMqttKafka() throws IOException + { + initJson("mqtt/asyncapi.yaml"); + List specs = new ArrayList<>(); + specs.add(new AsyncapiConfig("mqtt-api", 1, "mqtt/asyncapi.yaml", new Asyncapi())); + + + AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() + .inject(Function.identity()) + .specs(specs) + .mqttKafka(AsyncapiMqttKafkaConfig.builder().channels(AsyncapiChannelsConfig.builder() + .sessions("sessionsChannel") + .messages("messagesChannel") + .retained("retainedChannel") + .build()) + .build()) + .build(); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"specs\":" + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"sessionsChannel\"," + + "\"messages\":\"messagesChannel\"," + + "\"retained\":\"retainedChannel\"" + + "}" + + "}" + + "}")); + } } diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapterTest.java new file mode 100644 index 0000000000..fb8b93e070 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapterTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class AsyncapiWithConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new AsyncapiWithConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadWith() + { + String text = "{\"api-id\":\"test\",\"operation-id\":\"testOperation\"}"; + + AsyncapiWithConfig with = jsonb.fromJson(text, AsyncapiWithConfig.class); + + assertThat(with, not(nullValue())); + assertThat(with.apiId, equalTo("test")); + } + + @Test + public void shouldWriteWith() + { + AsyncapiWithConfig with = new AsyncapiWithConfig("test", "testOperation"); + + String text = jsonb.toJson(with); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo("{\"api-id\":\"test\",\"operation-id\":\"testOperation\"}")); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/proxy/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/proxy/AsyncapiIT.java new file mode 100644 index 0000000000..ce7ed7df1b --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/proxy/AsyncapiIT.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.proxy; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config") + .external("asyncapi_kafka0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("proxy.mqtt.kafka.yaml") + @Specification({ + "${asyncapi}/proxy.mqtt.publish/client", + "${asyncapi}/proxy.kafka.publish/server" + }) + public void shouldPublish() throws Exception + { + k3po.finish(); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfig.java index 3b4747193c..a6483a85bc 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfig.java @@ -15,6 +15,7 @@ package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; import java.util.List; +import java.util.function.Function; import io.aklivity.zilla.runtime.engine.config.ConditionConfig; @@ -23,7 +24,18 @@ public class MqttKafkaConditionConfig extends ConditionConfig public final List topics; public final MqttKafkaConditionKind kind; - public MqttKafkaConditionConfig( + public static MqttKafkaConditionConfigBuilder builder() + { + return new MqttKafkaConditionConfigBuilder<>(MqttKafkaConditionConfig.class::cast); + } + + public static MqttKafkaConditionConfigBuilder builder( + Function mapper) + { + return new MqttKafkaConditionConfigBuilder<>(mapper); + } + + MqttKafkaConditionConfig( List topics, MqttKafkaConditionKind kind) { diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfigBuilder.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfigBuilder.java new file mode 100644 index 0000000000..b01f32a24a --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfigBuilder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class MqttKafkaConditionConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private final List topics; + private MqttKafkaConditionKind kind; + + MqttKafkaConditionConfigBuilder( + Function mapper) + { + this.mapper = mapper; + this.topics = new ArrayList<>(); + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public MqttKafkaConditionConfigBuilder topic( + String topic) + { + this.topics.add(topic); + return this; + } + + public MqttKafkaConditionConfigBuilder kind( + MqttKafkaConditionKind kind) + { + this.kind = kind; + return this; + } + + @Override + public T build() + { + return mapper.apply(new MqttKafkaConditionConfig(topics, kind)); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfig.java similarity index 68% rename from runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfig.java rename to runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfig.java index a7634f39bb..4c60c7c599 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfig.java @@ -12,9 +12,10 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; import java.util.List; +import java.util.function.Function; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; @@ -24,7 +25,18 @@ public class MqttKafkaOptionsConfig extends OptionsConfig public final String serverRef; public final List clients; - public MqttKafkaOptionsConfig( + public static MqttKafkaOptionsConfigBuilder builder() + { + return new MqttKafkaOptionsConfigBuilder<>(MqttKafkaOptionsConfig.class::cast); + } + + public static MqttKafkaOptionsConfigBuilder builder( + Function mapper) + { + return new MqttKafkaOptionsConfigBuilder<>(mapper); + } + + MqttKafkaOptionsConfig( MqttKafkaTopicsConfig topics, String serverRef, List clients) diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfigBuilder.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfigBuilder.java new file mode 100644 index 0000000000..f1fc734608 --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfigBuilder.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public class MqttKafkaOptionsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private MqttKafkaTopicsConfig topics; + private String serverRef; + private List clients; + + MqttKafkaOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + public MqttKafkaOptionsConfigBuilder topics( + MqttKafkaTopicsConfig topics) + { + this.topics = topics; + return this; + } + + public MqttKafkaTopicsConfigBuilder> topics() + { + return new MqttKafkaTopicsConfigBuilder<>(this::topics); + } + + public MqttKafkaOptionsConfigBuilder serverRef( + String serverRef) + { + this.serverRef = serverRef; + return this; + } + + public MqttKafkaOptionsConfigBuilder clients( + List clients) + { + this.clients = clients; + return this; + } + + + @Override + public T build() + { + return mapper.apply(new MqttKafkaOptionsConfig(topics, serverRef, clients)); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaRouteConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java similarity index 89% rename from runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaRouteConfig.java rename to runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java index 3eea614b53..ed1d8ada79 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaRouteConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java @@ -12,7 +12,7 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; import static java.util.stream.Collectors.toList; @@ -20,8 +20,8 @@ import java.util.Optional; import java.util.function.LongPredicate; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfig; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaConditionMatcher; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaWithResolver; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; import io.aklivity.zilla.runtime.engine.config.RouteConfig; @@ -55,7 +55,7 @@ public MqttKafkaRouteConfig( this.authorized = route.authorized; } - boolean authorized( + public boolean authorized( long authorization) { return authorized.test(authorization); diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaTopicsConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfig.java similarity index 67% rename from runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaTopicsConfig.java rename to runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfig.java index 9a5c5066e1..c721daaf49 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaTopicsConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfig.java @@ -12,8 +12,9 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; +import java.util.function.Function; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; @@ -23,7 +24,18 @@ public class MqttKafkaTopicsConfig public final String16FW messages; public final String16FW retained; - public MqttKafkaTopicsConfig( + public static MqttKafkaTopicsConfigBuilder builder() + { + return new MqttKafkaTopicsConfigBuilder<>(MqttKafkaTopicsConfig.class::cast); + } + + public static MqttKafkaTopicsConfigBuilder builder( + Function mapper) + { + return new MqttKafkaTopicsConfigBuilder<>(mapper); + } + + MqttKafkaTopicsConfig( String16FW sessions, String16FW messages, String16FW retained) diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfigBuilder.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfigBuilder.java new file mode 100644 index 0000000000..a35d35cbd1 --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfigBuilder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public class MqttKafkaTopicsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private String sessions; + private String messages; + private String retained; + + MqttKafkaTopicsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + public MqttKafkaTopicsConfigBuilder sessions( + String sessions) + { + this.sessions = sessions; + return this; + } + + public MqttKafkaTopicsConfigBuilder messages( + String messages) + { + this.messages = messages; + return this; + } + + public MqttKafkaTopicsConfigBuilder retained( + String retained) + { + this.retained = retained; + return this; + } + + + @Override + public T build() + { + return mapper.apply( + new MqttKafkaTopicsConfig(new String16FW(sessions), new String16FW(messages), new String16FW(retained))); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfig.java new file mode 100644 index 0000000000..7a1cebb8d6 --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.WithConfig; + +public class MqttKafkaWithConfig extends WithConfig +{ + public final String messages; + + public static MqttKafkaWithConfigBuilder builder() + { + return new MqttKafkaWithConfigBuilder<>(MqttKafkaWithConfig.class::cast); + } + + public static MqttKafkaWithConfigBuilder builder( + Function mapper) + { + return new MqttKafkaWithConfigBuilder<>(mapper); + } + + MqttKafkaWithConfig( + String messages) + { + this.messages = messages; + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfigBuilder.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfigBuilder.java new file mode 100644 index 0000000000..3bb635e78e --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfigBuilder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.WithConfig; + +public final class MqttKafkaWithConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String messages; + + MqttKafkaWithConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public MqttKafkaWithConfigBuilder messages( + String messages) + { + this.messages = messages; + return this; + } + @Override + public T build() + { + return mapper.apply(new MqttKafkaWithConfig(messages)); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaBindingConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaBindingConfig.java index afcd7c7d6e..a152f8f2d0 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaBindingConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaBindingConfig.java @@ -25,6 +25,8 @@ import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaSessionFactory; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttTopicFilterFW; diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapter.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapter.java index 2902f92a1d..92c447c090 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapter.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapter.java @@ -14,9 +14,6 @@ */ package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; -import java.util.ArrayList; -import java.util.List; - import jakarta.json.Json; import jakarta.json.JsonArray; import jakarta.json.JsonArrayBuilder; @@ -25,6 +22,7 @@ import jakarta.json.bind.adapter.JsonbAdapter; import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfigBuilder; import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaBinding; import io.aklivity.zilla.runtime.engine.config.ConditionConfig; @@ -74,30 +72,21 @@ else if (mqttKafkaCondition.kind == MqttKafkaConditionKind.PUBLISH) public ConditionConfig adaptFromJson( JsonObject object) { - List topics = new ArrayList<>(); - MqttKafkaConditionKind kind = null; + MqttKafkaConditionConfigBuilder builder = MqttKafkaConditionConfig.builder(); if (object.containsKey(SUBSCRIBE_NAME)) { - kind = MqttKafkaConditionKind.SUBSCRIBE; + builder.kind(MqttKafkaConditionKind.SUBSCRIBE); JsonArray subscribesJson = object.getJsonArray(SUBSCRIBE_NAME); - subscribesJson.forEach(s -> - { - String topic = s.asJsonObject().getString(TOPIC_NAME); - topics.add(topic); - }); + subscribesJson.forEach(s -> builder.topic(s.asJsonObject().getString(TOPIC_NAME))); } else if (object.containsKey(PUBLISH_NAME)) { - kind = MqttKafkaConditionKind.PUBLISH; + builder.kind(MqttKafkaConditionKind.PUBLISH); JsonArray publishesJson = object.getJsonArray(PUBLISH_NAME); - publishesJson.forEach(p -> - { - String topic = p.asJsonObject().getString(TOPIC_NAME); - topics.add(topic); - }); + publishesJson.forEach(p -> builder.topic(p.asJsonObject().getString(TOPIC_NAME))); } - return new MqttKafkaConditionConfig(topics, kind); + return builder.build(); } } diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapter.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapter.java index a9a0ded706..bd6f9662d9 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapter.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapter.java @@ -25,6 +25,9 @@ import jakarta.json.JsonObjectBuilder; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaTopicsConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaBinding; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; @@ -104,8 +107,9 @@ public JsonObject adaptToJson( public OptionsConfig adaptFromJson( JsonObject object) { + MqttKafkaOptionsConfigBuilder options = MqttKafkaOptionsConfig.builder(); JsonObject topics = object.getJsonObject(TOPICS_NAME); - String server = object.getString(SERVER_NAME, null); + options.serverRef(object.getString(SERVER_NAME, null)); JsonArray clientsJson = object.getJsonArray(CLIENTS_NAME); List clients = new ArrayList<>(); @@ -116,13 +120,14 @@ public OptionsConfig adaptFromJson( clients.add(clientsJson.getString(i)); } } + options.clients(clients); - String16FW newSessions = new String16FW(topics.getString(SESSIONS_NAME)); - String16FW newMessages = new String16FW(topics.getString(MESSAGES_NAME)); - String16FW newRetained = new String16FW(topics.getString(RETAINED_NAME)); + options.topics(MqttKafkaTopicsConfig.builder() + .sessions(topics.getString(SESSIONS_NAME)) + .messages(topics.getString(MESSAGES_NAME)) + .retained(topics.getString(RETAINED_NAME)) + .build()); - MqttKafkaTopicsConfig newTopics = new MqttKafkaTopicsConfig(newSessions, newMessages, newRetained); - - return new MqttKafkaOptionsConfig(newTopics, server, clients); + return options.build(); } } diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapter.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapter.java index 0f345bf758..eaf7b913e9 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapter.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapter.java @@ -19,6 +19,7 @@ import jakarta.json.JsonObjectBuilder; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaBinding; import io.aklivity.zilla.runtime.engine.config.WithConfig; import io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi; @@ -57,6 +58,8 @@ public WithConfig adaptFromJson( ? object.getString(MESSAGES_NAME) : null; - return new MqttKafkaWithConfig(topic); + return MqttKafkaWithConfig.builder() + .messages(topic) + .build(); } } diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithResolver.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithResolver.java index 4d8850821e..d0ca895a2c 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithResolver.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithResolver.java @@ -15,6 +15,8 @@ package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; public class MqttKafkaWithResolver diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java index 9b9a809083..eee1fbe44a 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java @@ -36,10 +36,10 @@ import org.agrona.collections.Long2ObjectHashMap; import org.agrona.concurrent.UnsafeBuffer; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaBindingConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaHeaderHelper; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaGroup; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaOffsetMetadata; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaTopicPartition; diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java index 4d371e38ed..1a19f7783d 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java @@ -49,11 +49,11 @@ import org.agrona.collections.Object2ObjectHashMap; import org.agrona.concurrent.UnsafeBuffer; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.InstanceId; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaBindingConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaHeaderHelper; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaGroup; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaOffsetMetadata; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaOffsetMetadataHelper; @@ -1431,6 +1431,7 @@ private void doCreateSessionStream( .flags(sessionFlags) .expiry((int) TimeUnit.MILLISECONDS.toSeconds(sessionExpiryMillis)) .subscribeQosMax(MQTT_KAFKA_MAX_QOS) + .publishQosMax(MQTT_KAFKA_MAX_QOS) .capabilities(MQTT_KAFKA_CAPABILITIES) .clientId(clientId); @@ -4086,8 +4087,11 @@ private void onKafkaData( final KafkaMetaDataExFW kafkaMetaDataEx = kafkaDataEx.meta(); final Array32FW partitions = kafkaMetaDataEx.partitions(); - delegate.onPartitionsFetched(traceId, authorization, topic, partitions, this); - doKafkaEnd(traceId, authorization); + if (!MqttKafkaState.initialClosed(state)) + { + delegate.onPartitionsFetched(traceId, authorization, topic, partitions, this); + doKafkaEnd(traceId, authorization); + } } private void onKafkaEnd( diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeFactory.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeFactory.java index fde1562c52..1f53782aaf 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeFactory.java @@ -14,7 +14,6 @@ */ package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream; - import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaSessionFactory.MQTT_CLIENTS_GROUP_ID; import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttPublishFlags.RETAIN; import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttSubscribeFlags.NO_LOCAL; @@ -26,7 +25,6 @@ import static java.time.Instant.now; import static java.util.concurrent.TimeUnit.SECONDS; - import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -47,10 +45,10 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaBindingConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaHeaderHelper; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.Flyweight; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.KafkaCapabilities; diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapterTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapterTest.java index e739a320f4..761ce1a449 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapterTest.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapterTest.java @@ -19,8 +19,6 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import java.util.List; - import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; @@ -88,7 +86,10 @@ public void shouldReadCondition() @Test public void shouldWriteSubscribeCondition() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig(List.of("test"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("test") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); String text = jsonb.toJson(condition); @@ -107,7 +108,10 @@ public void shouldWriteSubscribeCondition() @Test public void shouldWritePublishCondition() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig(List.of("test"), MqttKafkaConditionKind.PUBLISH); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("test") + .kind(MqttKafkaConditionKind.PUBLISH) + .build(); String text = jsonb.toJson(condition); diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcherTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcherTest.java index 9afe0f9459..6331b0384f 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcherTest.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcherTest.java @@ -17,8 +17,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.util.List; - import org.junit.Test; import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfig; @@ -29,8 +27,10 @@ public class MqttKafkaConditionMatcherTest @Test public void shouldMatchSimpleConditions() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig(List.of("/some/hierarchical/topic/name"), - MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("/some/hierarchical/topic/name") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertTrue(matcher.matches("/some/hierarchical/topic/name")); @@ -47,9 +47,10 @@ public void shouldMatchSimpleConditions() @Test public void shouldNotMatchSimpleConditions() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("/some/hierarchical/topic/name"), - MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("/some/hierarchical/topic/name") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertFalse(matcher.matches("/some/+")); @@ -61,8 +62,10 @@ public void shouldNotMatchSimpleConditions() @Test public void shouldMatchSimpleConditions2() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("/some/hierarchical/topic"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("/some/hierarchical/topic") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertTrue(matcher.matches("/some/hierarchical/topic")); @@ -79,8 +82,10 @@ public void shouldMatchSimpleConditions2() @Test public void shouldNotMatchSimpleConditions2() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("/some/hierarchical/topic"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("/some/hierarchical/topic") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertFalse(matcher.matches("/some/+")); @@ -92,8 +97,10 @@ public void shouldNotMatchSimpleConditions2() @Test public void shouldMatchWildcardConditions() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("device/#"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("device/#") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertTrue(matcher.matches("device/one")); @@ -107,8 +114,10 @@ public void shouldMatchWildcardConditions() @Test public void shouldNotMatchWildcardConditions() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("device/#"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("device/#") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertFalse(matcher.matches("/device/one")); diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapterTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapterTest.java index cc74c235da..c69492f9ca 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapterTest.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapterTest.java @@ -28,7 +28,8 @@ import org.junit.Before; import org.junit.Test; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaTopicsConfig; public class MqttKafkaOptionsConfigAdapterTest { @@ -78,13 +79,15 @@ public void shouldReadOptions() @Test public void shouldWriteOptions() { - MqttKafkaOptionsConfig options = new MqttKafkaOptionsConfig( - new MqttKafkaTopicsConfig( - new String16FW("sessions"), - new String16FW("messages"), - new String16FW("retained")), - "mqtt-1.example.com:1883", - Arrays.asList("/clients/{identity}/#", "/department/clients/{identity}/#")); + MqttKafkaOptionsConfig options = MqttKafkaOptionsConfig.builder() + .topics(MqttKafkaTopicsConfig.builder() + .sessions("sessions") + .messages("messages") + .retained("retained") + .build()) + .serverRef("mqtt-1.example.com:1883") + .clients(Arrays.asList("/clients/{identity}/#", "/department/clients/{identity}/#")) + .build(); String text = jsonb.toJson(options); @@ -131,11 +134,13 @@ public void shouldReadOptionsWithoutClients() @Test public void shouldWriteOptionsWithoutClients() { - MqttKafkaOptionsConfig options = new MqttKafkaOptionsConfig( - new MqttKafkaTopicsConfig( - new String16FW("sessions"), - new String16FW("messages"), - new String16FW("retained")), null, null); + MqttKafkaOptionsConfig options = MqttKafkaOptionsConfig.builder() + .topics(MqttKafkaTopicsConfig.builder() + .sessions("sessions") + .messages("messages") + .retained("retained") + .build()) + .build(); String text = jsonb.toJson(options); diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapterTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapterTest.java index 0662393ef2..a4f5cd3b94 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapterTest.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapterTest.java @@ -26,6 +26,8 @@ import org.junit.Before; import org.junit.Test; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig; + public class MqttKafkaWithConfigAdapterTest { private Jsonb jsonb; @@ -53,7 +55,7 @@ public void shouldReadWith() @Test public void shouldWriteWith() { - MqttKafkaWithConfig with = new MqttKafkaWithConfig("test"); + MqttKafkaWithConfig with = MqttKafkaWithConfig.builder().messages("test").build(); String text = jsonb.toJson(with); From e31adeac50752ef579c9fc416a9de88f91634f29 Mon Sep 17 00:00:00 2001 From: bmaidics Date: Thu, 29 Feb 2024 21:05:55 +0100 Subject: [PATCH 19/25] Fix NPE in KafkaSignalStream (#823) --- .../kafka/internal/stream/MqttKafkaSessionFactory.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 1a19f7783d..4e0e7a7e40 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java @@ -1913,9 +1913,12 @@ private void doKafkaEnd( { if (!MqttKafkaState.initialClosed(state)) { - state = MqttKafkaState.closeInitial(state); + if (MqttKafkaState.initialOpening(state)) + { + state = MqttKafkaState.closeInitial(state); - doEnd(kafka, originId, routedId, initialId, 0, 0, 0, traceId, authorization); + doEnd(kafka, originId, routedId, initialId, 0, 0, 0, traceId, authorization); + } signaler.cancel(reconnectAt); reconnectAt = NO_CANCEL_ID; From f858a9f6ff9a22894df1ba7d380838730b6fea7a Mon Sep 17 00:00:00 2001 From: bmaidics Date: Thu, 29 Feb 2024 21:06:09 +0100 Subject: [PATCH 20/25] Fix early flush sending for retained stream (#822) --- .../mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eee1fbe44a..2554a12fe6 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java @@ -793,7 +793,7 @@ private void doMqttWindow( final int newInitialMax = retainedFlag ? Math.max(messages.initialMax, retained.initialMax) : messages.initialMax; if (MqttKafkaState.initialOpened(messages.state) && - (!retainedFlag || MqttKafkaState.initialOpened(retained.state)) && + (!retainAvailable || MqttKafkaState.initialOpened(retained.state)) && (initialAck != newInitialAck || initialMax != newInitialMax)) { initialAck = newInitialAck; From e3dc0e1d557ff733a442df98439d7620ebdc8533 Mon Sep 17 00:00:00 2001 From: Akram Yakubov Date: Thu, 29 Feb 2024 16:11:05 -0800 Subject: [PATCH 21/25] Support http to kafka proxy using openapi.yaml and asyncapi.yaml (#810) --- cloud/docker-image/pom.xml | 6 + .../src/main/docker/zpm.json.template | 1 + .../asyncapi/config/http/asyncapi.yaml | 32 +- .../asyncapi/http/create.pet/client.rpt | 2 +- .../asyncapi/http/create.pet/server.rpt | 2 +- .../asyncapi/AsyncapiFunctionsTest.java | 4 +- .../asyncapi/config/AsyncapiParser.java | 132 ++ .../AsyncapiProxyCompositeBindingAdapter.java | 1 - .../config/AsyncapiOptionsConfigAdapter.java | 116 +- .../src/main/moditect/module-info.java | 1 + .../binding-openapi-asyncapi.spec/COPYRIGHT | 12 + .../binding-openapi-asyncapi.spec/LICENSE | 114 ++ .../binding-openapi-asyncapi.spec/NOTICE | 28 + .../NOTICE.template | 16 + incubator/binding-openapi-asyncapi.spec/mvnw | 310 +++ .../binding-openapi-asyncapi.spec/mvnw.cmd | 182 ++ .../binding-openapi-asyncapi.spec/pom.xml | 181 ++ .../asyncapi/OpenapiAsyncapiSpecs.java | 13 +- .../src/main/moditect/module-info.java | 15 +- ...kaazing.k3po.lang.el.spi.FunctionMapperSpi | 1 + .../asyncapi/config/asyncapi/petstore.yaml | 101 + .../asyncapi/config/openapi/petstore.yaml | 94 + .../openapi/asyncapi/config/proxy.yaml | 33 + .../schema/openapi.asyncapi.schema.patch.json | 145 ++ .../streams/asyncapi/create.pet/client.rpt | 60 + .../streams/asyncapi/create.pet/server.rpt | 55 + .../streams/openapi/create.pet/client.rpt | 47 + .../streams/openapi/create.pet/server.rpt | 50 + .../openapi/asyncapi/config/SchemaTest.java | 44 + .../openapi/asyncapi/streams/AsyncapiIT.java | 48 + .../openapi/asyncapi/streams/OpenapiIT.java | 48 + incubator/binding-openapi-asyncapi/COPYRIGHT | 12 + incubator/binding-openapi-asyncapi/LICENSE | 114 ++ incubator/binding-openapi-asyncapi/NOTICE | 35 + .../binding-openapi-asyncapi/NOTICE.template | 16 + incubator/binding-openapi-asyncapi/mvnw | 310 +++ incubator/binding-openapi-asyncapi/mvnw.cmd | 182 ++ incubator/binding-openapi-asyncapi/pom.xml | 284 +++ .../asyncapi/config/AsyncapiConfig.java | 37 + .../config/OpenapiAsyncapiOptionsConfig.java | 58 + .../config/OpenapiAsyncapiSpecConfig.java | 31 + .../asyncapi/config/OpenapiConfig.java | 38 + .../internal/OpenapiAsyncapiBinding.java | 67 + .../OpenapiAsyncapiBindingContext.java | 74 + .../OpenapiAsyncapiBindingFactorySpi.java | 36 + .../OpenapiAsyncapiConfiguration.java | 34 + .../OpenapiAsyncCompositeBindingAdapter.java | 150 ++ .../config/OpenapiAsyncapiBindingConfig.java | 95 + .../OpenapiAsyncapiConditionConfig.java | 47 + ...OpenapiAsyncapiConditionConfigAdapter.java | 69 + .../OpenapiAsyncapiOptionsConfigAdapter.java | 138 ++ .../config/OpenapiAsyncapiRouteConfig.java | 57 + .../config/OpenapiAsyncapiWithConfig.java | 31 + .../OpenapiAsyncapiWithConfigAdapter.java | 72 + .../streams/OpenapiAsyncapiProxyFactory.java | 1762 +++++++++++++++++ .../streams/OpenapiAsyncapiState.java | 134 ++ .../streams/OpenapiAsyncapiStreamFactory.java | 27 + .../src/main/moditect/module-info.java | 42 + ...a.runtime.engine.binding.BindingFactorySpi | 1 + ...e.engine.config.CompositeBindingAdapterSpi | 1 + ...me.engine.config.ConditionConfigAdapterSpi | 1 + ...time.engine.config.OptionsConfigAdapterSpi | 1 + ...runtime.engine.config.WithConfigAdapterSpi | 1 + ...apiAsyncapiConditionConfigAdapterTest.java | 71 + ...enapiAsyncapiOptionsConfigAdapterTest.java | 128 ++ .../OpenapiAsyncapiWithConfigAdapterTest.java | 63 + .../internal/streams/OpenapiAsyncapiIT.java | 59 + incubator/binding-openapi.spec/LICENSE | 1 + incubator/binding-openapi.spec/NOTICE | 18 +- .../binding-openapi.spec/NOTICE.template | 20 +- incubator/binding-openapi.spec/pom.xml | 2 +- .../binding/openapi/OpenapiFunctions.java | 115 ++ ...kaazing.k3po.lang.el.spi.FunctionMapperSpi | 1 + .../main/resources/META-INF/zilla/openapi.idl | 2 +- .../specs/binding/openapi/config/client.yaml | 2 +- .../binding/openapi/config/server-secure.yaml | 2 +- .../specs/binding/openapi/config/server.yaml | 2 +- .../openapi/schema/openapi.schema.patch.json | 13 +- .../streams/openapi/create.pet/client.rpt | 2 +- .../streams/openapi/create.pet/server.rpt | 2 +- .../internal/OpenapiFunctionsTest.java | 31 +- incubator/binding-openapi/NOTICE.template | 3 + .../binding/openapi/config/OpenapiConfig.java | 13 +- .../binding/openapi/config/OpenapiParser.java | 134 ++ .../internal/config/OpenapiBindingConfig.java | 18 +- .../OpenapiClientCompositeBindingAdapter.java | 22 +- .../config/OpenapiOptionsConfigAdapter.java | 151 +- .../OpenapiServerCompositeBindingAdapter.java | 27 +- .../internal/model/OpenApiOperation.java | 2 +- .../model/{OpenApi.java => Openapi.java} | 4 +- .../{PathItem.java => OpenapiPathItem.java} | 2 +- ...java => OpenapiResponseByContentType.java} | 2 +- .../streams/OpenapiServerFactory.java | 9 +- .../internal/view/OpenApiOperationView.java | 8 +- .../internal/view/OpenApiOperationsView.java | 6 +- .../internal/view/OpenApiPathView.java | 6 +- .../src/main/moditect/module-info.java | 1 + .../OpenapiOptionsConfigAdapterTest.java | 16 +- .../internal/streams/OpenapiServerIT.java | 1 - incubator/pom.xml | 7 + .../config/HttpKafkaConditionConfig.java | 13 + .../HttpKafkaConditionConfigBuilder.java | 63 + .../config/HttpKafkaWithConfig.java | 17 +- .../config/HttpKafkaWithConfigBuilder.java | 65 + .../config/HttpKafkaWithFetchConfig.java | 17 +- .../HttpKafkaWithFetchConfigBuilder.java | 70 + .../config/HttpKafkaWithFetchMergeConfig.java | 15 +- .../HttpKafkaWithFetchMergeConfigBuilder.java | 69 + ...HttpKafkaWithProduceAsyncHeaderConfig.java | 42 + ...kaWithProduceAsyncHeaderConfigBuilder.java | 62 + .../config/HttpKafkaWithProduceConfig.java | 14 +- .../HttpKafkaWithProduceConfigBuilder.java | 96 + .../HttpKafkaWithProduceOverrideConfig.java | 42 + ...KafkaWithProduceOverrideConfigBuilder.java | 61 + .../HttpKafkaConditionConfigAdapter.java | 5 +- .../internal/config/HttpKafkaRouteConfig.java | 1 + .../config/HttpKafkaWithConfigAdapter.java | 1 + .../HttpKafkaWithFetchConfigAdapter.java | 18 +- .../HttpKafkaWithProduceConfigAdapter.java | 26 +- .../config/HttpKafkaWithResolver.java | 6 + .../HttpKafkaConditionConfigAdapterTest.java | 5 +- .../HttpKafkaWithConfigAdapterTest.java | 119 +- 122 files changed, 7229 insertions(+), 415 deletions(-) create mode 100644 incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiParser.java create mode 100644 incubator/binding-openapi-asyncapi.spec/COPYRIGHT create mode 100644 incubator/binding-openapi-asyncapi.spec/LICENSE create mode 100644 incubator/binding-openapi-asyncapi.spec/NOTICE create mode 100644 incubator/binding-openapi-asyncapi.spec/NOTICE.template create mode 100755 incubator/binding-openapi-asyncapi.spec/mvnw create mode 100644 incubator/binding-openapi-asyncapi.spec/mvnw.cmd create mode 100644 incubator/binding-openapi-asyncapi.spec/pom.xml rename runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceOverrideConfig.java => incubator/binding-openapi-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/OpenapiAsyncapiSpecs.java (64%) rename runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceAsyncHeaderConfig.java => incubator/binding-openapi-asyncapi.spec/src/main/moditect/module-info.java (63%) create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/asyncapi/petstore.yaml create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/openapi/petstore.yaml create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/proxy.yaml create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/client.rpt create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/server.rpt create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/client.rpt create mode 100644 incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/server.rpt create mode 100644 incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/SchemaTest.java create mode 100644 incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/AsyncapiIT.java create mode 100644 incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/OpenapiIT.java create mode 100644 incubator/binding-openapi-asyncapi/COPYRIGHT create mode 100644 incubator/binding-openapi-asyncapi/LICENSE create mode 100644 incubator/binding-openapi-asyncapi/NOTICE create mode 100644 incubator/binding-openapi-asyncapi/NOTICE.template create mode 100755 incubator/binding-openapi-asyncapi/mvnw create mode 100644 incubator/binding-openapi-asyncapi/mvnw.cmd create mode 100644 incubator/binding-openapi-asyncapi/pom.xml create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/AsyncapiConfig.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiOptionsConfig.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiSpecConfig.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiConfig.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBinding.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingContext.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingFactorySpi.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiConfiguration.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiBindingConfig.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfig.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapter.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapter.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiRouteConfig.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfig.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapter.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiProxyFactory.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiState.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiStreamFactory.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/moditect/module-info.java create mode 100644 incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi create mode 100644 incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi create mode 100644 incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi create mode 100644 incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi create mode 100644 incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi create mode 100644 incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapterTest.java create mode 100644 incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapterTest.java create mode 100644 incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapterTest.java create mode 100644 incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java create mode 100644 incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiParser.java rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApi.java => Openapi.java} (92%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{PathItem.java => OpenapiPathItem.java} (97%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{ResponseByContentType.java => OpenapiResponseByContentType.java} (94%) create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfigBuilder.java rename runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/{internal => }/config/HttpKafkaWithConfig.java (71%) create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfigBuilder.java rename runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/{internal => }/config/HttpKafkaWithFetchConfig.java (66%) create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfigBuilder.java rename runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/{internal => }/config/HttpKafkaWithFetchMergeConfig.java (65%) create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfigBuilder.java create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfig.java create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfigBuilder.java rename runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/{internal => }/config/HttpKafkaWithProduceConfig.java (85%) create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfigBuilder.java create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfig.java create mode 100644 runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfigBuilder.java diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index d70ae69cac..6509f55e19 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -55,6 +55,12 @@ ${project.version} runtime + + ${project.groupId} + binding-openapi-asyncapi + ${project.version} + runtime + ${project.groupId} binding-echo diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template index 94335ae43d..790c062053 100644 --- a/cloud/docker-image/src/main/docker/zpm.json.template +++ b/cloud/docker-image/src/main/docker/zpm.json.template @@ -28,6 +28,7 @@ "io.aklivity.zilla:binding-mqtt", "io.aklivity.zilla:binding-mqtt-kafka", "io.aklivity.zilla:binding-openapi", + "io.aklivity.zilla:binding-openapi-asyncapi", "io.aklivity.zilla:binding-proxy", "io.aklivity.zilla:binding-sse", "io.aklivity.zilla:binding-sse-kafka", diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml index 8e5a0d70ac..5c4b7a0dbe 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml @@ -30,15 +30,20 @@ defaultContentType: application/json channels: pets: address: /pets + messages: + pet: + $ref: '#/components/messages/pet' showPetById: address: /pets/{id} + messages: + pet: + $ref: '#/components/messages/pet' operations: createPet: action: send bindings: http: - type: request method: POST channel: $ref: '#/channels/pets' @@ -46,7 +51,6 @@ operations: action: receive bindings: http: - type: request method: GET channel: $ref: '#/channels/pets' @@ -54,7 +58,6 @@ operations: action: receive bindings: http: - type: request method: GET query: type: object @@ -68,3 +71,26 @@ components: correlationIds: petsCorrelationId: location: '$message.header#/idempotency-key' + schemas: + petPayload: + type: object + properties: + id: + type: integer + minimum: 0 + description: Pet id. + name: + type: string + description: Pet name. + tag: + type: string + description: Tag. + messages: + pet: + name: Pet + title: Pet + summary: >- + Inform about Pet. + contentType: application/json + payload: + $ref: '#/components/schemas/petPayload' diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt index e169ce5939..6e0038ff3d 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt @@ -20,7 +20,7 @@ connect "zilla://streams/asyncapi0" write zilla:begin.ext ${asyncapi:beginEx() .typeId(zilla:id("asyncapi")) - .apiId(3833148448) + .apiId(759838734) .operationId("createPet") .extension(http:beginEx() .typeId(zilla:id("http")) diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt index daf5dc67dd..92f26191d2 100644 --- a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt @@ -22,7 +22,7 @@ accepted read zilla:begin.ext ${asyncapi:matchBeginEx() .typeId(zilla:id("asyncapi")) - .apiId(3833148448) + .apiId(759838734) .operationId("createPet") .extension(http:beginEx() .typeId(zilla:id("http")) diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java index 5970c60168..44ab1567fd 100644 --- a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java @@ -63,6 +63,7 @@ public void shouldMatchAsyncapiBeginExtensionOnly() throws Exception { BytesMatcher matcher = AsyncapiFunctions.matchBeginEx() .typeId(0x00) + .apiId(1L) .extension(new byte[] {1}) .build(); @@ -71,6 +72,7 @@ public void shouldMatchAsyncapiBeginExtensionOnly() throws Exception new AsyncapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) .typeId(0x00) + .apiId(1L) .extension(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build()) .build(); @@ -82,7 +84,7 @@ public void shouldMatchAsyncapiBeginExtension() throws Exception { BytesMatcher matcher = AsyncapiFunctions.matchBeginEx() .typeId(0x00) - .apiId(1) + .apiId(1L) .operationId("operationId") .extension(new byte[] {1}) .build(); diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiParser.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiParser.java new file mode 100644 index 0000000000..403b966819 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiParser.java @@ -0,0 +1,132 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import static java.util.Collections.unmodifiableMap; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.InputStream; +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import org.agrona.collections.Object2ObjectHashMap; +import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.JsonValidationService; +import org.leadpony.justify.api.ProblemHandler; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.engine.config.ConfigException; + +public class AsyncapiParser +{ + private final Map schemas; + + public AsyncapiParser() + { + Map schemas = new Object2ObjectHashMap<>(); + schemas.put("2.6.0", schema("2.6.0")); + schemas.put("3.0.0", schema("3.0.0")); + this.schemas = unmodifiableMap(schemas); + } + + public Asyncapi parse( + String asyncapiText) + { + Asyncapi asyncapi = null; + + List errors = new LinkedList<>(); + + try + { + String asyncApiVersion = detectAsyncApiVersion(asyncapiText); + + JsonValidationService service = JsonValidationService.newInstance(); + ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); + JsonSchema schema = schemas.get(asyncApiVersion); + + service.createReader(new StringReader(asyncapiText), schema, handler).read(); + + Jsonb jsonb = JsonbBuilder.create(); + + asyncapi = jsonb.fromJson(asyncapiText, Asyncapi.class); + } + catch (Exception ex) + { + errors.add(ex); + } + + if (!errors.isEmpty()) + { + Exception ex = errors.remove(0); + errors.forEach(ex::addSuppressed); + rethrowUnchecked(ex); + } + + return asyncapi; + } + + private JsonSchema schema( + String version) + { + InputStream schemaInput = null; + + if (version.startsWith("2.6")) + { + schemaInput = AsyncapiBinding.class.getResourceAsStream("schema/asyncapi.2.6.schema.json"); + } + else if (version.startsWith("3.0")) + { + schemaInput = AsyncapiBinding.class.getResourceAsStream("schema/asyncapi.3.0.schema.json"); + } + + JsonValidationService service = JsonValidationService.newInstance(); + + return service.createSchemaReaderFactoryBuilder() + .withSpecVersionDetection(true) + .build() + .createSchemaReader(schemaInput) + .read(); + } + + private String detectAsyncApiVersion( + String openapiText) + { + try (JsonReader reader = Json.createReader(new StringReader(openapiText))) + { + JsonObject json = reader.readObject(); + if (json.containsKey("asyncapi")) + { + return json.getString("asyncapi"); + } + else + { + throw new IllegalArgumentException("Unable to determine AsyncApi version."); + } + } + catch (Exception e) + { + throw new RuntimeException("Error reading AsyncApi document.", e); + } + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java index 03386813a4..c85e764503 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java @@ -127,7 +127,6 @@ public BindingConfigBuilder injectMqttKafkaRoutes( { break inject; } - final AsyncapiOperation whenOperation = mqttAsyncapi.operations.get(condition.operationId); final AsyncapiChannelView channel = AsyncapiChannelView.of(mqttAsyncapi.channels, whenOperation.channel); final MqttKafkaConditionKind kind = whenOperation.action.equals(ASYNCAPI_SEND_ACTION_NAME) ? diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java index 1b55629f9c..e7c61ed268 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java @@ -15,13 +15,8 @@ package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; import static java.util.stream.Collectors.toList; -import static org.agrona.LangUtil.rethrowUnchecked; -import java.io.InputStream; -import java.io.Reader; -import java.io.StringReader; import java.nio.charset.StandardCharsets; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -30,18 +25,10 @@ import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonReader; import jakarta.json.JsonString; -import jakarta.json.JsonStructure; import jakarta.json.JsonValue; -import jakarta.json.bind.Jsonb; -import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.adapter.JsonbAdapter; -import org.leadpony.justify.api.JsonSchema; -import org.leadpony.justify.api.JsonValidationService; -import org.leadpony.justify.api.ProblemHandler; - import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiChannelsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiChannelsConfigBuilder; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; @@ -49,6 +36,7 @@ import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiMqttKafkaConfigBuilder; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiParser; import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; @@ -56,7 +44,6 @@ import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; -import io.aklivity.zilla.runtime.engine.config.ConfigException; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; @@ -74,13 +61,21 @@ public final class AsyncapiOptionsConfigAdapter implements OptionsConfigAdapterS private static final String MESSAGES_NAME = "messages"; private static final String RETAINED_NAME = "retained"; - private CRC32C crc; + private final AsyncapiParser parser; + private final CRC32C crc; + private OptionsConfigAdapter tcpOptions; private OptionsConfigAdapter tlsOptions; private OptionsConfigAdapter httpOptions; private OptionsConfigAdapter kafkaOptions; private Function readURL; + public AsyncapiOptionsConfigAdapter() + { + this.parser = new AsyncapiParser(); + this.crc = new CRC32C(); + } + public Kind kind() { return Kind.BINDING; @@ -244,7 +239,6 @@ public void adaptContext( this.httpOptions.adaptType("http"); this.kafkaOptions = new OptionsConfigAdapter(Kind.BINDING, context); this.kafkaOptions.adaptType("kafka"); - this.crc = new CRC32C(); } private List asListAsyncapis( @@ -264,97 +258,9 @@ private AsyncapiConfig asAsyncapi( crc.reset(); crc.update(specText.getBytes(StandardCharsets.UTF_8)); final long apiId = crc.getValue(); - Asyncapi asyncapi = parseAsyncapi(specText); + Asyncapi asyncapi = parser.parse(specText); return new AsyncapiConfig(apiLabel, apiId, location, asyncapi); } - private Asyncapi parseAsyncapi( - String asyncapiText) - { - Asyncapi asyncapi = null; - if (validateAsyncapiSchema(asyncapiText)) - { - try (Jsonb jsonb = JsonbBuilder.create()) - { - asyncapi = jsonb.fromJson(asyncapiText, Asyncapi.class); - } - catch (Exception ex) - { - rethrowUnchecked(ex); - } - } - return asyncapi; - } - - private boolean validateAsyncapiSchema( - String asyncapiText) - { - List errors = new LinkedList<>(); - - boolean valid = false; - - try - { - JsonValidationService service = JsonValidationService.newInstance(); - - String version = detectAsyncapiVersion(asyncapiText); - InputStream schemaInput = selectSchemaPathForVersion(version); - - JsonSchema schema = service.readSchema(schemaInput); - ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); - - String readable = asyncapiText.stripTrailing(); - Reader asyncapiReader = new StringReader(readable); - - JsonReader reader = service.createReader(asyncapiReader, schema, handler); - - JsonStructure json = reader.read(); - valid = json != null; - } - catch (Exception ex) - { - errors.add(ex); - } - - return valid; - } - - private String detectAsyncapiVersion( - String asyncapiText) - { - try (JsonReader reader = Json.createReader(new StringReader(asyncapiText))) - { - JsonObject json = reader.readObject(); - if (json.containsKey("asyncapi")) - { - return json.getString("asyncapi"); - } - else - { - throw new IllegalArgumentException("Unable to determine AsyncAPI version."); - } - } - catch (Exception e) - { - throw new RuntimeException("Error reading AsyncAPI document.", e); - } - } - - private InputStream selectSchemaPathForVersion( - String version) - { - if (version.startsWith("3.0")) - { - return AsyncapiBinding.class.getResourceAsStream("schema/asyncapi.3.0.schema.json"); - } - else if (version.startsWith("2.6")) - { - return AsyncapiBinding.class.getResourceAsStream("schema/asyncapi.2.6.schema.json"); - } - else - { - throw new IllegalArgumentException("Unsupported AsyncAPI version: " + version); - } - } } diff --git a/incubator/binding-asyncapi/src/main/moditect/module-info.java b/incubator/binding-asyncapi/src/main/moditect/module-info.java index 380da1403e..fe146ddc7b 100644 --- a/incubator/binding-asyncapi/src/main/moditect/module-info.java +++ b/incubator/binding-asyncapi/src/main/moditect/module-info.java @@ -31,6 +31,7 @@ requires org.leadpony.justify; opens io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + opens io.aklivity.zilla.runtime.binding.asyncapi.internal.view; exports io.aklivity.zilla.runtime.binding.asyncapi.config; diff --git a/incubator/binding-openapi-asyncapi.spec/COPYRIGHT b/incubator/binding-openapi-asyncapi.spec/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-openapi-asyncapi.spec/LICENSE b/incubator/binding-openapi-asyncapi.spec/LICENSE new file mode 100644 index 0000000000..1184b83429 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. diff --git a/incubator/binding-openapi-asyncapi.spec/NOTICE b/incubator/binding-openapi-asyncapi.spec/NOTICE new file mode 100644 index 0000000000..f1570ef67e --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/NOTICE @@ -0,0 +1,28 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::incubator::binding-asyncapi.spec under The Apache Software License, Version 2.0 + zilla::incubator::binding-openapi.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-kafka.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-mqtt-kafka.spec under Aklivity Community License Agreement + zilla::specs::binding-mqtt.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi-asyncapi.spec/NOTICE.template b/incubator/binding-openapi-asyncapi.spec/NOTICE.template new file mode 100644 index 0000000000..67b08ca131 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/NOTICE.template @@ -0,0 +1,16 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi-asyncapi.spec/mvnw b/incubator/binding-openapi-asyncapi.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi-asyncapi.spec/mvnw.cmd b/incubator/binding-openapi-asyncapi.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi-asyncapi.spec/pom.xml b/incubator/binding-openapi-asyncapi.spec/pom.xml new file mode 100644 index 0000000000..3157d47c92 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/pom.xml @@ -0,0 +1,181 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + binding-openapi-asyncapi.spec + zilla::incubator::binding-openapi-asyncapi.spec + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 11 + 11 + 0.96 + 1 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-asyncapi.spec + ${project.version} + + + ${project.groupId} + binding-openapi.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + + + ${project.groupId} + binding-kafka.spec + ${project.version} + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http openapi + io.aklivity.zilla.specs.binding.openapi.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/binding/openapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceOverrideConfig.java b/incubator/binding-openapi-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/OpenapiAsyncapiSpecs.java similarity index 64% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceOverrideConfig.java rename to incubator/binding-openapi-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/OpenapiAsyncapiSpecs.java index db09b44830..81bcc26c90 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceOverrideConfig.java +++ b/incubator/binding-openapi-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/OpenapiAsyncapiSpecs.java @@ -12,18 +12,11 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.specs.binding.openapi.asyncapi; -public final class HttpKafkaWithProduceOverrideConfig +public final class OpenapiAsyncapiSpecs { - public final String name; - public final String value; - - public HttpKafkaWithProduceOverrideConfig( - String name, - String value) + private OpenapiAsyncapiSpecs() { - this.name = name; - this.value = value; } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceAsyncHeaderConfig.java b/incubator/binding-openapi-asyncapi.spec/src/main/moditect/module-info.java similarity index 63% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceAsyncHeaderConfig.java rename to incubator/binding-openapi-asyncapi.spec/src/main/moditect/module-info.java index cb5ee737f1..d88214430f 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceAsyncHeaderConfig.java +++ b/incubator/binding-openapi-asyncapi.spec/src/main/moditect/module-info.java @@ -12,18 +12,7 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; - -public final class HttpKafkaWithProduceAsyncHeaderConfig +open module io.aklivity.zilla.specs.binding.openapi.asyncapi { - public final String name; - public final String value; - - public HttpKafkaWithProduceAsyncHeaderConfig( - String name, - String value) - { - this.name = name; - this.value = value; - } + requires transitive io.aklivity.zilla.specs.engine; } diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi b/incubator/binding-openapi-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi new file mode 100644 index 0000000000..cb5c82fd24 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi @@ -0,0 +1 @@ +io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions$Mapper diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/asyncapi/petstore.yaml b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/asyncapi/petstore.yaml new file mode 100644 index 0000000000..f21b4b91e5 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/asyncapi/petstore.yaml @@ -0,0 +1,101 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +asyncapi: 3.0.0 +info: + title: Petstore Kafka API + version: 1.0.0 +defaultContentType: application/json +servers: + host-connections: + host: 'localhost:9092' + protocol: kafka-secure + description: Test broker + tags: + - name: 'kind:remote' + description: This server is a remote server. Not exposed by the application. + - name: 'visibility:private' + description: This resource is private and only available to certain users. +channels: + petstore: + address: 'petstore' + messages: + pet: + $ref: '#/components/messages/pet' + description: The topic on which pet values may be produced and consumed. +operations: + listPets: + action: receive + channel: + $ref: '#/channels/petstore' + summary: >- + List all pets. + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/petstore/messages/pet' + createPets: + action: send + channel: + $ref: '#/channels/petstore' + summary: >- + Create a pet. + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/petstore/messages/pet' +components: + messages: + pet: + name: Pet + title: Pet + summary: >- + Inform about Pet. + contentType: application/json + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/petPayload' + schemas: + petPayload: + type: object + properties: + id: + type: integer + minimum: 0 + description: Pet id. + name: + type: string + description: Pet name. + tag: + type: string + description: Tag. + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + operationTraits: + kafka: + bindings: + kafka: + clientId: + type: string + enum: + - my-app-id diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/openapi/petstore.yaml b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/openapi/petstore.yaml new file mode 100644 index 0000000000..8b2dc0b5f9 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/openapi/petstore.yaml @@ -0,0 +1,94 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:9090 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/proxy.yaml b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/proxy.yaml new file mode 100644 index 0000000000..c07f511c9e --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/proxy.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +bindings: + composite0: + type: openapi-asyncapi + kind: proxy + options: + specs: + openapi: + openapi-id: openapi/petstore.yaml + asyncapi: + asyncapi-id: asyncapi/petstore.yaml + routes: + - when: + - api-id: openapi-id + exit: asyncapi_client0 + with: + api-id: asyncapi-id diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json new file mode 100644 index 0000000000..c05fb5b9cd --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json @@ -0,0 +1,145 @@ +[ + { + "op": "add", + "path": "/$defs/binding/properties/type/enum/-", + "value": "openapi-asyncapi" + }, + { + "op": "add", + "path": "/$defs/binding/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "openapi-asyncapi" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "openapi-asyncapi" + }, + "kind": + { + "enum": [ "proxy"] + }, + "vault": false, + "options": + { + "properties": + { + "specs": + { + "title": "Specs", + "type": "object", + "properties": + { + "openapi": + { + "title": "Openapi", + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "type": "string" + } + }, + "maxProperties": 1 + }, + "asyncapi": + { + "title": "Asyncapi", + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "type": "string" + } + }, + "maxProperties": 1 + } + }, + "required": [ "openapi", "asyncapi" ] + } + }, + "additionalProperties": false + }, + "routes": + { + "items": + { + "properties": + { + "when": + { + "items": + { + "properties": + { + "api-id": + { + "title": "ApiId", + "type": "string" + }, + "operation-id": + { + "title": "OperationId", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "with": + { + "properties": + { + "api-id": + { + "title": "ApiId", + "type": "string" + }, + "operation-id": + { + "title": "OperationId", + "type": "string" + } + } + } + }, + "required": + [ + "with" + ] + } + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "required": + [ + "routes" + ] + } + ] + } + } + } +] diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/client.rpt b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/client.rpt new file mode 100644 index 0000000000..b6d858e440 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/client.rpt @@ -0,0 +1,60 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/asyncapi_client0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(1977467119) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("petstore") + .partition(-1, -2) + .ackMode("IN_SYNC_REPLICAS") + .build() + .build()) + .build()} + +connected + +write option zilla:flags "init" +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .timestamp(newTimestamp) + .partition(-1, -1) + .build() + .build()} +write zilla:data.empty +write flush + +write option zilla:flags "none" +write "{\"id\": 1, \"name\": \"Dog\", \"tag\": \"test\"}" +write flush + +write option zilla:flags "fin" +write zilla:data.empty +write flush + +write close +read closed diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/server.rpt b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/server.rpt new file mode 100644 index 0000000000..d0ae5c6302 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/server.rpt @@ -0,0 +1,55 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +accept "zilla://streams/asyncapi_client0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(1977467119) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("petstore") + .partition(-1, -2) + .ackMode("IN_SYNC_REPLICAS") + .build() + .build()) + .build()} + +connected + +read option zilla:flags "init" +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .partition(-1, -1) + .build() + .build()} +read zilla:data.empty + +read option zilla:flags "none" +read "{\"id\": 1, \"name\": \"Dog\", \"tag\": \"test\"}" + +read option zilla:flags "fin" +read zilla:data.empty + +read closed +write close diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/client.rpt b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/client.rpt new file mode 100644 index 0000000000..bb5f2f69d8 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/client.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .apiId(1807802422) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} +connected + +write "{\"id\": 1, \"name\": \"Dog\", \"tag\": \"test\"}" +write close + +read zilla:begin.ext ${openapi:matchBeginEx() + .typeId(zilla:id("openapi")) + .apiId(1807802422) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .build()) + .build()} diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/server.rpt b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/server.rpt new file mode 100644 index 0000000000..cc1f7e4d9e --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/server.rpt @@ -0,0 +1,50 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +accept "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .apiId(1807802422) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} + +connected + +read "{\"id\": 1, \"name\": \"Dog\", \"tag\": \"test\"}" +read closed + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .apiId(1807802422) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .build()) + .build()} +write flush diff --git a/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/SchemaTest.java b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/SchemaTest.java new file mode 100644 index 0000000000..44c1f57cf1 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/SchemaTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.asyncapi.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.JsonObject; + +import org.junit.Rule; +import org.junit.Test; + +import io.aklivity.zilla.specs.engine.config.ConfigSchemaRule; + +public class SchemaTest +{ + @Rule + public final ConfigSchemaRule schema = new ConfigSchemaRule() + .schemaPatch("io/aklivity/zilla/specs/binding/openapi/asyncapi/" + + "schema/openapi.asyncapi.schema.patch.json") + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/asyncapi/config"); + + + @Test + public void shouldValidateProxy() + { + JsonObject config = schema.validate("proxy.yaml"); + + assertThat(config, not(nullValue())); + } +} diff --git a/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/AsyncapiIT.java b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/AsyncapiIT.java new file mode 100644 index 0000000000..a4c5f29642 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/AsyncapiIT.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${asyncapi}/create.pet/client", + "${asyncapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/OpenapiIT.java b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/OpenapiIT.java new file mode 100644 index 0000000000..a655b0dcfa --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/OpenapiIT.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class OpenapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${openapi}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi-asyncapi/COPYRIGHT b/incubator/binding-openapi-asyncapi/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-openapi-asyncapi/LICENSE b/incubator/binding-openapi-asyncapi/LICENSE new file mode 100644 index 0000000000..f6abb6327b --- /dev/null +++ b/incubator/binding-openapi-asyncapi/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/binding-openapi-asyncapi/NOTICE b/incubator/binding-openapi-asyncapi/NOTICE new file mode 100644 index 0000000000..9a20dc7d2a --- /dev/null +++ b/incubator/binding-openapi-asyncapi/NOTICE @@ -0,0 +1,35 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + ICU4J under Unicode/ICU License + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Jackson-dataformat-YAML under The Apache Software License, Version 2.0 + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + JSR 353 (JSON Processing) Default Provider under Dual license consisting of the CDDL v1.1 and GPL v2 + org.leadpony.justify under The Apache Software License, Version 2.0 + SnakeYAML under Apache License, Version 2.0 + zilla::incubator::binding-asyncapi under Aklivity Community License Agreement + zilla::incubator::binding-openapi under Aklivity Community License Agreement + zilla::incubator::catalog-inline under Aklivity Community License Agreement + zilla::incubator::model-core under Aklivity Community License Agreement + zilla::incubator::model-json under Aklivity Community License Agreement + zilla::runtime::binding-http under The Apache Software License, Version 2.0 + zilla::runtime::binding-http-kafka under Aklivity Community License Agreement + zilla::runtime::binding-kafka under The Apache Software License, Version 2.0 + zilla::runtime::binding-tcp under The Apache Software License, Version 2.0 + zilla::runtime::binding-tls under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi-asyncapi/NOTICE.template b/incubator/binding-openapi-asyncapi/NOTICE.template new file mode 100644 index 0000000000..67b08ca131 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/NOTICE.template @@ -0,0 +1,16 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi-asyncapi/mvnw b/incubator/binding-openapi-asyncapi/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi-asyncapi/mvnw.cmd b/incubator/binding-openapi-asyncapi/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi-asyncapi/pom.xml b/incubator/binding-openapi-asyncapi/pom.xml new file mode 100644 index 0000000000..e0faa31bae --- /dev/null +++ b/incubator/binding-openapi-asyncapi/pom.xml @@ -0,0 +1,284 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + develop-SNAPSHOT + ../pom.xml + + + binding-openapi-asyncapi + zilla::incubator::binding-openapi-asyncapi + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.60 + 4 + + + + + ${project.groupId} + binding-openapi-asyncapi.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + io.aklivity.zilla + binding-openapi + ${project.version} + + + io.aklivity.zilla + binding-asyncapi + ${project.version} + + + io.aklivity.zilla + binding-http-kafka + ${project.version} + + + io.aklivity.zilla + binding-http + ${project.version} + + + io.aklivity.zilla + binding-kafka + ${project.version} + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http kafka asyncapi openapi + io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types + + + + + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + binding-openapi-asyncapi.spec + + + ^\Qio/aklivity/zilla/specs/binding/openapi/asyncapi/\E + io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/ + + + + + io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json + ${project.build.directory}/classes + + + + unpack-openapi + generate-sources + + unpack + + + + + ${project.groupId} + binding-openapi-asyncapi.spec + ${project.version} + ${basedir}/target/test-classes + **\/*.yaml + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/AsyncapiConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/AsyncapiConfig.java new file mode 100644 index 0000000000..7644d177ea --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/AsyncapiConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; + +public class AsyncapiConfig +{ + public final String apiLabel; + public final long apiId; + public final String location; + public final Asyncapi asyncapi; + + public AsyncapiConfig( + String apiLabel, + long apiId, + String location, + Asyncapi asyncapi) + { + this.apiLabel = apiLabel; + this.apiId = apiId; + this.location = location; + this.asyncapi = asyncapi; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiOptionsConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiOptionsConfig.java new file mode 100644 index 0000000000..82b9c9ff68 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiOptionsConfig.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class OpenapiAsyncapiOptionsConfig extends OptionsConfig +{ + public final OpenapiAsyncapiSpecConfig specs; + + public OpenapiAsyncapiOptionsConfig( + OpenapiAsyncapiSpecConfig specs) + { + this.specs = specs; + } + + public long resolveOpenapiApiId( + String apiLabel) + { + long apiId = -1; + for (OpenapiConfig c : specs.openapi) + { + if (c.apiLabel.equals(apiLabel)) + { + apiId = c.apiId; + break; + } + } + return apiId; + } + + public long resolveAsyncapiApiId( + String apiLabel) + { + long apiId = -1; + for (AsyncapiConfig c : specs.asyncapi) + { + if (c.apiLabel.equals(apiLabel)) + { + apiId = c.apiId; + break; + } + } + return apiId; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiSpecConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiSpecConfig.java new file mode 100644 index 0000000000..da9b2ba82d --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiSpecConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + +import java.util.Set; + +public class OpenapiAsyncapiSpecConfig +{ + public final Set openapi; + public final Set asyncapi; + + public OpenapiAsyncapiSpecConfig( + Set openapi, + Set asyncapi) + { + this.openapi = openapi; + this.asyncapi = asyncapi; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiConfig.java new file mode 100644 index 0000000000..5def7dce71 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; + +public class OpenapiConfig +{ + public final String apiLabel; + public final long apiId; + public final String location; + public final Openapi openapi; + + public OpenapiConfig( + String apiLabel, + long apiId, + String location, + Openapi openapi) + { + this.apiLabel = apiLabel; + this.apiId = apiId; + this.location = location; + this.openapi = openapi; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBinding.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBinding.java new file mode 100644 index 0000000000..ae3a0b6784 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBinding.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class OpenapiAsyncapiBinding implements Binding +{ + public static final String NAME = "openapi-asyncapi"; + + private final OpenapiAsyncapiConfiguration config; + + OpenapiAsyncapiBinding( + OpenapiAsyncapiConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/openapi.asyncapi.schema.patch.json"); + } + + @Override + public String originType( + KindConfig kind) + { + return kind == KindConfig.CLIENT ? NAME : null; + } + + @Override + public String routedType( + KindConfig kind) + { + return kind == KindConfig.SERVER ? NAME : null; + } + + @Override + public OpenapiAsyncapiBindingContext supply( + EngineContext context) + { + return new OpenapiAsyncapiBindingContext(config, context); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingContext.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingContext.java new file mode 100644 index 0000000000..85d5747da1 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingContext.java @@ -0,0 +1,74 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; + +import java.util.EnumMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams.OpenapiAsyncapiProxyFactory; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams.OpenapiAsyncapiStreamFactory; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +final class OpenapiAsyncapiBindingContext implements BindingContext +{ + private final Map factories; + + OpenapiAsyncapiBindingContext( + OpenapiAsyncapiConfiguration config, + EngineContext context) + { + Map factories = new EnumMap<>(KindConfig.class); + factories.put(PROXY, new OpenapiAsyncapiProxyFactory(config, context)); + this.factories = factories; + } + + @Override + public BindingHandler attach( + BindingConfig binding) + { + OpenapiAsyncapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.attach(binding); + } + + return factory; + } + + @Override + public void detach( + BindingConfig binding) + { + OpenapiAsyncapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.detach(binding.id); + } + } + + @Override + public String toString() + { + return String.format("%s %s", getClass().getSimpleName(), factories); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingFactorySpi.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingFactorySpi.java new file mode 100644 index 0000000000..5a9df3c110 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingFactorySpi.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal; + +import io.aklivity.zilla.runtime.common.feature.Incubating; +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi; + +@Incubating +public final class OpenapiAsyncapiBindingFactorySpi implements BindingFactorySpi +{ + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public OpenapiAsyncapiBinding create( + Configuration config) + { + return new OpenapiAsyncapiBinding(new OpenapiAsyncapiConfiguration(config)); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiConfiguration.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiConfiguration.java new file mode 100644 index 0000000000..3ebcbcf0e5 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class OpenapiAsyncapiConfiguration extends Configuration +{ + private static final ConfigurationDef OPENAPI_ASYNC_CONFIG; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.binding.openapi.asyncapi"); + OPENAPI_ASYNC_CONFIG = config; + } + + public OpenapiAsyncapiConfiguration( + Configuration config) + { + super(OPENAPI_ASYNC_CONFIG, config); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java new file mode 100644 index 0000000000..3badc8d43c --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java @@ -0,0 +1,150 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; +import static java.util.stream.Collectors.toList; + +import java.util.List; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaConditionConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiSpecConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; + +public final class OpenapiAsyncCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + OpenapiAsyncapiOptionsConfig options = (OpenapiAsyncapiOptionsConfig) binding.options; + + List routes = binding.routes.stream() + .map(r -> new OpenapiAsyncapiRouteConfig(r, options::resolveOpenapiApiId)) + .collect(toList()); + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/http_kafka", binding.qname)) + .binding() + .name("http_kafka0") + .type("http-kafka") + .kind(PROXY) + .inject(b -> this.injectHttpKafkaRoutes(b, binding.qname, options.specs, routes)) + .build() + .build() + .build(); + } + + private BindingConfigBuilder injectHttpKafkaRoutes( + BindingConfigBuilder binding, + String qname, + OpenapiAsyncapiSpecConfig spec, + List routes) + { + for (OpenapiAsyncapiRouteConfig route : routes) + { + for (OpenapiAsyncapiConditionConfig condition : route.when) + { + Openapi openapi = spec.openapi.stream() + .filter(o -> o.apiLabel.equals(condition.apiId)) + .findFirst() + .get().openapi; + Asyncapi asyncapi = spec.asyncapi.stream() + .filter(o -> o.apiLabel.equals(route.with.apiId)) + .findFirst() + .get().asyncapi; + + for (String item : openapi.paths.keySet()) + { + OpenApiPathView path = OpenApiPathView.of(openapi.paths.get(item)); + for (String method : path.methods().keySet()) + { + final String operationId = condition.operationId != null ? + condition.operationId : path.methods().get(method).operationId; + + final AsyncapiOperation operation = asyncapi.operations.entrySet().stream() + .filter(f -> f.getKey().equals(operationId)) + .map(v -> v.getValue()) + .findFirst() + .get(); + + final AsyncapiChannelView channel = AsyncapiChannelView + .of(asyncapi.channels, operation.channel); + + binding + .route() + .exit(qname) + .when(HttpKafkaConditionConfig::builder) + .method(method) + .path(item) + .build() + .inject(r -> injectHttpKafkaRouteWith(r, operation.action, channel.address())) + .build(); + } + } + } + } + + return binding; + } + + private RouteConfigBuilder injectHttpKafkaRouteWith( + RouteConfigBuilder route, + String action, + String address) + { + final HttpKafkaWithConfigBuilder newWith = HttpKafkaWithConfig.builder(); + + switch (action) + { + case "receive": + newWith.fetch(HttpKafkaWithFetchConfig.builder() + .topic(address) + .build()); + break; + case "send": + newWith.produce(HttpKafkaWithProduceConfig.builder() + .topic(address) + .acks("in_sync_replicas") + .build()); + break; + } + + route.with(newWith.build()); + + return route; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiBindingConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiBindingConfig.java new file mode 100644 index 0000000000..9162fe0510 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiBindingConfig.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static java.util.stream.Collector.of; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; + +import java.util.List; + +import org.agrona.collections.IntHashSet; +import org.agrona.collections.Long2LongHashMap; + +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiAsyncapiBindingConfig +{ + public final long id; + public final String name; + public final KindConfig kind; + public final OpenapiAsyncapiOptionsConfig options; + public final List routes; + + private final IntHashSet httpKafkaOrigins; + private final Long2LongHashMap resolvedIds; + + public OpenapiAsyncapiBindingConfig( + BindingConfig binding) + { + this.id = binding.id; + this.name = binding.name; + this.kind = binding.kind; + this.options = OpenapiAsyncapiOptionsConfig.class.cast(binding.options); + + this.routes = binding.routes.stream() + .map(r -> new OpenapiAsyncapiRouteConfig(r, options::resolveOpenapiApiId)) + .collect(toList()); + + this.resolvedIds = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http-kafka")) + .collect(of( + () -> new Long2LongHashMap(-1), + (m, r) -> m.put(options.specs.openapi.stream().findFirst().get().apiId, r.id), + (m, r) -> m, + IDENTITY_FINISH + )); + + this.httpKafkaOrigins = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http-kafka")) + .map(b -> NamespacedId.namespaceId(b.id)) + .collect(toCollection(IntHashSet::new)); + } + + public boolean isCompositeNamespace( + int namespaceId) + { + return httpKafkaOrigins.contains(namespaceId); + } + + public long resolveResolvedId( + long apiId) + { + return resolvedIds.get(apiId); + } + + public OpenapiAsyncapiRouteConfig resolve( + long authorization, + long apiId) + { + return routes.stream() + .filter(r -> r.authorized(authorization) && r.matches(apiId)) + .findFirst() + .orElse(null); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfig.java new file mode 100644 index 0000000000..21aeb203ac --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; + +public class OpenapiAsyncapiConditionConfig extends ConditionConfig +{ + public final String apiId; + public final String operationId; + + public OpenapiAsyncapiConditionConfig( + String apiId, + String operationId) + { + this.apiId = apiId; + this.operationId = operationId; + } + + public boolean matches( + long apiId, + Function supplyApiId) + { + return matchesApiId(apiId, supplyApiId); + } + + private boolean matchesApiId( + long apiId, + Function supplyApiId) + { + return supplyApiId.apply(this.apiId) == apiId; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapter.java new file mode 100644 index 0000000000..83e9358679 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; +import io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi; + +public final class OpenapiAsyncapiConditionConfigAdapter implements ConditionConfigAdapterSpi, + JsonbAdapter +{ + private static final String API_ID_NAME = "api-id"; + private static final String OPERATION_ID_NAME = "operation-id"; + + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + ConditionConfig condition) + { + OpenapiAsyncapiConditionConfig asyncapiCondition = (OpenapiAsyncapiConditionConfig) condition; + JsonObjectBuilder object = Json.createObjectBuilder(); + + object.add(API_ID_NAME, asyncapiCondition.apiId); + + if (asyncapiCondition.operationId != null) + { + object.add(OPERATION_ID_NAME, asyncapiCondition.operationId); + } + + return object.build(); + } + + @Override + public ConditionConfig adaptFromJson( + JsonObject object) + { + String apiId = object.containsKey(API_ID_NAME) + ? object.getString(API_ID_NAME) + : null; + + String operationId = object.containsKey(OPERATION_ID_NAME) + ? object.getString(OPERATION_ID_NAME) + : null; + + return new OpenapiAsyncapiConditionConfig(apiId, operationId); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapter.java new file mode 100644 index 0000000000..d76e26b215 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapter.java @@ -0,0 +1,138 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.unmodifiableSet; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.zip.CRC32C; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonString; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiParser; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiSpecConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiParser; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public final class OpenapiAsyncapiOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + private static final String OPENAPI_NAME = "openapi"; + private static final String ASYNCAPI_NAME = "asyncapi"; + private static final String SPECS_NAME = "specs"; + + private final CRC32C crc; + + private final OpenapiParser openapiParser = new OpenapiParser(); + private final AsyncapiParser asyncapiParser = new AsyncapiParser(); + + private Function readURL; + + public OpenapiAsyncapiOptionsConfigAdapter() + { + crc = new CRC32C(); + } + + @Override + public Kind kind() + { + return Kind.BINDING; + } + + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + OpenapiAsyncapiOptionsConfig proxyOptions = (OpenapiAsyncapiOptionsConfig) options; + + JsonObjectBuilder object = Json.createObjectBuilder(); + JsonObjectBuilder spec = Json.createObjectBuilder(); + + JsonObjectBuilder openapi = Json.createObjectBuilder(); + proxyOptions.specs.openapi.forEach(o -> openapi.add(o.apiLabel, o.location)); + spec.add(OPENAPI_NAME, openapi); + + JsonObjectBuilder asyncapi = Json.createObjectBuilder(); + proxyOptions.specs.asyncapi.forEach(a -> asyncapi.add(a.apiLabel, a.location)); + spec.add(ASYNCAPI_NAME, asyncapi); + + object.add(SPECS_NAME, spec); + + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + JsonObject specs = object.getJsonObject(SPECS_NAME); + + JsonObject openapi = specs.getJsonObject(OPENAPI_NAME); + Set openapis = new LinkedHashSet<>(); + openapi.forEach((n, v) -> + { + final String location = JsonString.class.cast(v).getString(); + final String specText = readURL.apply(location); + final String apiLabel = n; + crc.reset(); + crc.update(specText.getBytes(UTF_8)); + final long apiId = crc.getValue(); + openapis.add(new OpenapiConfig(apiLabel, apiId, location, openapiParser.parse(specText))); + }); + + JsonObject asyncapi = specs.getJsonObject(ASYNCAPI_NAME); + Set asyncapis = new LinkedHashSet<>(); + asyncapi.forEach((n, v) -> + { + final String location = JsonString.class.cast(v).getString(); + final String specText = readURL.apply(location); + final String apiLabel = n; + crc.reset(); + crc.update(specText.getBytes(UTF_8)); + final long apiId = crc.getValue(); + asyncapis.add(new AsyncapiConfig(apiLabel, apiId, location, asyncapiParser.parse(specText))); + }); + + OpenapiAsyncapiSpecConfig specConfig = new OpenapiAsyncapiSpecConfig( + unmodifiableSet(openapis), unmodifiableSet(asyncapis)); + + return new OpenapiAsyncapiOptionsConfig(specConfig); + } + + @Override + public void adaptContext( + ConfigAdapterContext context) + { + this.readURL = context::readURL; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiRouteConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiRouteConfig.java new file mode 100644 index 0000000000..fa1982fc31 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiRouteConfig.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.function.Function; +import java.util.function.LongPredicate; + +import io.aklivity.zilla.runtime.engine.config.RouteConfig; + +public final class OpenapiAsyncapiRouteConfig +{ + public final long id; + public final OpenapiAsyncapiWithConfig with; + private final Function supplyApiId; + public final List when; + private final LongPredicate authorized; + + public OpenapiAsyncapiRouteConfig( + RouteConfig route, + Function supplyApiId) + { + this.id = route.id; + this.authorized = route.authorized; + this.when = route.when.stream() + .map(OpenapiAsyncapiConditionConfig.class::cast) + .collect(toList()); + this.with = (OpenapiAsyncapiWithConfig) route.with; + this.supplyApiId = supplyApiId; + } + + boolean authorized( + long authorization) + { + return authorized.test(authorization); + } + + boolean matches( + long apiId) + { + return when.isEmpty() || when.stream().anyMatch(m -> m.matches(apiId, supplyApiId)); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfig.java new file mode 100644 index 0000000000..a370ec4f05 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import io.aklivity.zilla.runtime.engine.config.WithConfig; + +public class OpenapiAsyncapiWithConfig extends WithConfig +{ + public final String apiId; + public final String operationId; + + public OpenapiAsyncapiWithConfig( + String apiId, + String operationId) + { + this.apiId = apiId; + this.operationId = operationId; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapter.java new file mode 100644 index 0000000000..4221465e8b --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; +import io.aklivity.zilla.runtime.engine.config.WithConfig; +import io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi; + +public class OpenapiAsyncapiWithConfigAdapter implements WithConfigAdapterSpi, JsonbAdapter +{ + private static final String API_ID_NAME = "api-id"; + private static final String OPERATION_ID_NAME = "operation-id"; + + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + WithConfig with) + { + OpenapiAsyncapiWithConfig config = (OpenapiAsyncapiWithConfig) with; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (config.apiId != null) + { + object.add(API_ID_NAME, config.apiId); + } + + if (config.operationId != null) + { + object.add(OPERATION_ID_NAME, config.operationId); + } + + return object.build(); + } + + @Override + public WithConfig adaptFromJson( + JsonObject object) + { + String apiId = object.containsKey(API_ID_NAME) + ? object.getString(API_ID_NAME) + : null; + + String operationId = object.containsKey(OPERATION_ID_NAME) + ? object.getString(OPERATION_ID_NAME) + : null; + + return new OpenapiAsyncapiWithConfig(apiId, operationId); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiProxyFactory.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiProxyFactory.java new file mode 100644 index 0000000000..025b961fa3 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiProxyFactory.java @@ -0,0 +1,1762 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams; + +import java.util.function.Function; +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiConfiguration; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiBindingConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiRouteConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.AsyncapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.OpenapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiAsyncapiProxyFactory implements OpenapiAsyncapiStreamFactory +{ + private static final int UNKNOWN_COMPOSITE_RESOLVED_ID = -1; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); + private final OpenapiBeginExFW openapiBeginExRO = new OpenapiBeginExFW(); + + private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); + private final OpenapiBeginExFW.Builder openapiBeginExRW = new OpenapiBeginExFW.Builder(); + + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Function supplyTypeId; + private final Long2ObjectHashMap bindings; + private final Long2LongHashMap apiIds; + private final int openapiTypeId; + private final int asyncapiTypeId; + + private final OpenapiAsyncapiConfiguration config; + + public OpenapiAsyncapiProxyFactory( + OpenapiAsyncapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTypeId = context::supplyTypeId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.apiIds = new Long2LongHashMap(-1); + this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME); + this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); + } + + + @Override + public int routedTypeId() + { + return asyncapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + OpenapiAsyncapiBindingConfig apiBinding = new OpenapiAsyncapiBindingConfig(binding); + bindings.put(binding.id, apiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + + final OpenapiAsyncapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + if (!binding.isCompositeNamespace(NamespacedId.namespaceId(originId))) + { + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + final long apiId = openapiBeginEx.apiId(); + final String operationId = openapiBeginEx.operationId().asString(); + + final long compositeResolvedId = binding.resolveResolvedId(apiId); + apiIds.put(apiId, apiId); + + if (compositeResolvedId != UNKNOWN_COMPOSITE_RESOLVED_ID) + { + newStream = new OpenapiStream( + receiver, + originId, + routedId, + initialId, + apiId, + authorization, + compositeResolvedId, + operationId)::onOpenapiMessage; + } + } + else + { + final long apiId = apiIds.get(affinity); + final OpenapiAsyncapiRouteConfig route = binding.resolve(authorization, apiId); + + if (route != null) + { + final long clientApiId = binding.options.resolveAsyncapiApiId(route.with.apiId); + final String operationId = route.with.operationId; + newStream = new CompositeClientStream( + receiver, + originId, + routedId, + initialId, + authorization, + route.id, + affinity, + clientApiId, + operationId)::onCompositeClientMessage; + } + } + } + + return newStream; + } + + private final class OpenapiStream + { + private final CompositeServerStream delegate; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private OpenapiStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long compositeResolvedId, + String operationId) + { + this.delegate = + new CompositeServerStream(this, compositeResolvedId, compositeResolvedId, authorization, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onOpenapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onOpenapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onOpenapiData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onOpenpaiEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onOpenapiFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onOpenapiAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onOpenpaiWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onOpenapiReset(reset); + break; + default: + break; + } + } + + private void onOpenapiBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = OpenapiAsyncapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + delegate.doCompositeBegin(traceId, affinity, openapiBeginEx.extension()); + } + + private void onOpenapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onOpenpaiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiAsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onOpenapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onOpenapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiAsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeAbort(traceId, extension); + } + + private void doOpenapiReset( + long traceId) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + state = OpenapiAsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doOpenapiWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = delegate.initialAck; + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void doOpenapiBegin( + long traceId, + Flyweight extension) + { + state = OpenapiAsyncapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doOpenapiData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doOpenapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, reserved, extension); + } + + private void doOpenapiEnd( + long traceId, + OctetsFW extension) + { + if (OpenapiAsyncapiState.replyOpening(state) && !OpenapiAsyncapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = OpenapiAsyncapiState.closeReply(state); + } + + private void doOpenapiAbort( + long traceId) + { + if (OpenapiAsyncapiState.replyOpening(state) && !OpenapiAsyncapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = OpenapiAsyncapiState.closeInitial(state); + } + + private void onOpenapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = OpenapiAsyncapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onOpenpaiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeWindow(traceId, acknowledge, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doOpenapiReset(traceId); + doOpenapiAbort(traceId); + + delegate.cleanup(traceId); + } + } + + final class CompositeServerStream + { + private final String operationId; + private final long originId; + private final long routedId; + private final long authorization; + + private OpenapiStream delegate; + private long initialId; + private long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeServerStream( + OpenapiStream delegate, + long routedId, + long compositeResolvedId, + long authorization, + String operationId) + { + this.delegate = delegate; + this.originId = routedId; + this.routedId = compositeResolvedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onCompositeServerMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiAsyncapiState.openReply(state); + + final OpenapiBeginExFW openapiBeginEx = openapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(openapiTypeId) + .apiId(delegate.affinity) + .operationId(operationId) + .extension(extension) + .build(); + + delegate.doOpenapiBegin(traceId, openapiBeginEx); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final int flags = data.flags(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiData(traceId, flags, reserved, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiAbort(traceId); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiAsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doOpenapiReset(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiAsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doOpenapiWindow(authorization, traceId, budgetId, padding); + } + + private void doCompositeReset( + long traceId) + { + if (!OpenapiAsyncapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiAsyncapiState.closeReply(state); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doCompositeBegin( + long traceId, + long affinity, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialOpening(state)) + { + assert state == 0; + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.receiver = newStream(this::onCompositeServerMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, affinity, extension); + state = OpenapiAsyncapiState.openingInitial(state); + } + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiAsyncapiState.closeInitial(state); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiAsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + final class CompositeClientStream + { + private final long originId; + private final long routedId; + private final MessageConsumer sender; + private final long affinity; + private final long authorization; + private final AsyncapiStream delegate; + + private long initialId; + private long replyId; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeClientStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long authorization, + long resolvedId, + long affinity, + long apiId, + String operationId) + { + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.delegate = new AsyncapiStream(this, originId, resolvedId, authorization, apiId, operationId); + } + + private void onCompositeClientMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + + state = OpenapiAsyncapiState.openingInitial(state); + + delegate.doAsyncapiBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final int flags = data.flags(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiAsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiAsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiAbort(traceId, EMPTY_OCTETS); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + + replyAck = acknowledge; + + state = OpenapiAsyncapiState.closeReply(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doAsyncapiReset(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyPad = padding; + state = OpenapiAsyncapiState.openReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiWindow(authorization, traceId, budgetId, padding); + } + + private void doCompositeReset( + long traceId) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + state = OpenapiAsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + initialAck = Math.max(delegate.initialAck, 0); + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + replySeq = delegate.replySeq; + replyAck = delegate.replyAck; + replyMax = delegate.replyMax; + state = OpenapiAsyncapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + replySeq += reserved; + + assert replySeq <= replyAck + replyMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.replyClosed(state)) + { + replySeq = delegate.replySeq; + state = OpenapiAsyncapiState.closeReply(state); + + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, traceId, authorization, extension); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.replyClosed(state)) + { + replySeq = delegate.replySeq; + state = OpenapiAsyncapiState.closeReply(state); + + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + final class AsyncapiStream + { + private final long originId; + private final long routedId; + private final long authorization; + private final CompositeClientStream delegate; + private final long apiId; + + private long initialId; + private final String operationId; + private long replyId; + private MessageConsumer asyncapi; + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private AsyncapiStream( + CompositeClientStream delegate, + long originId, + long routedId, + long authorization, + long apiId, + String operationId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.operationId = operationId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.authorization = authorization; + this.apiId = apiId; + } + + private void onAsyncapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiWindow(window); + break; + default: + break; + } + } + + private void onAsyncapiBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiAsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + final OctetsFW asyncapiExtension = asyncapiBeginEx != null ? asyncapiBeginEx.extension() : EMPTY_OCTETS; + + delegate.doCompositeBegin(traceId, asyncapiExtension); + } + + private void onAsyncapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onAsyncapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeAbort(traceId, EMPTY_OCTETS); + } + + private void onAsyncapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiAsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doCompositeReset(traceId); + } + + + private void onAsyncapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiAsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeWindow(authorization, traceId, budgetId, padding); + } + + private void doAsyncapiReset( + long traceId) + { + if (!OpenapiAsyncapiState.replyClosed(state)) + { + doReset(asyncapi, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiAsyncapiState.closeReply(state); + } + } + + private void doAsyncapiWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(asyncapi, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doAsyncapiBegin( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialOpening(state)) + { + assert state == 0; + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .apiId(apiId) + .operationId(operationId) + .extension(extension) + .build(); + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.asyncapi = newStream(this::onAsyncapiMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, 0L, asyncapiBeginEx); + state = OpenapiAsyncapiState.openingInitial(state); + } + } + + private void doAsyncapiData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doAsyncapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doAsyncapiEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + doEnd(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiAsyncapiState.closeInitial(state); + } + } + + private void doAsyncapiAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + doAbort(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiAsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doAsyncapiAbort(traceId, EMPTY_OCTETS); + delegate.doCompositeReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiState.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiState.java new file mode 100644 index 0000000000..2a20838e7b --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiState.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams; + +public final class OpenapiAsyncapiState +{ + private static final int INITIAL_OPENING = 0x10; + private static final int INITIAL_OPENED = 0x20; + private static final int INITIAL_CLOSING = 0x40; + private static final int INITIAL_CLOSED = 0x80; + private static final int REPLY_OPENING = 0x01; + private static final int REPLY_OPENED = 0x02; + private static final int REPLY_CLOSING = 0x04; + private static final int REPLY_CLOSED = 0x08; + + static int openingInitial( + int state) + { + return state | INITIAL_OPENING; + } + + static int openInitial( + int state) + { + return openingInitial(state) | INITIAL_OPENED; + } + + static int closingInitial( + int state) + { + return state | INITIAL_CLOSING; + } + + static int closeInitial( + int state) + { + return closingInitial(state) | INITIAL_CLOSED; + } + + static boolean initialOpening( + int state) + { + return (state & INITIAL_OPENING) != 0; + } + + static boolean initialOpened( + int state) + { + return (state & INITIAL_OPENED) != 0; + } + + static boolean initialClosing( + int state) + { + return (state & INITIAL_CLOSING) != 0; + } + + static boolean initialClosed( + int state) + { + return (state & INITIAL_CLOSED) != 0; + } + + static boolean closed( + int state) + { + return initialClosed(state) && replyClosed(state); + } + + static int openingReply( + int state) + { + return state | REPLY_OPENING; + } + + static int openReply( + int state) + { + return state | REPLY_OPENED; + } + + static boolean replyOpening( + int state) + { + return (state & REPLY_OPENING) != 0; + } + + static boolean replyOpened( + int state) + { + return (state & REPLY_OPENED) != 0; + } + + static int closingReply( + int state) + { + return state | REPLY_CLOSING; + } + + static boolean replyClosing( + int state) + { + return (state & REPLY_CLOSING) != 0; + } + + static int closeReply( + int state) + { + return closingReply(state) | REPLY_CLOSED; + } + + static boolean replyClosed( + int state) + { + return (state & REPLY_CLOSED) != 0; + } + + private OpenapiAsyncapiState() + { + // utility + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiStreamFactory.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiStreamFactory.java new file mode 100644 index 0000000000..d115a8e498 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiStreamFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams; + +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public interface OpenapiAsyncapiStreamFactory extends BindingHandler +{ + void attach( + BindingConfig binding); + + void detach( + long bindingId); +} diff --git a/incubator/binding-openapi-asyncapi/src/main/moditect/module-info.java b/incubator/binding-openapi-asyncapi/src/main/moditect/module-info.java new file mode 100644 index 0000000000..f01ec71dfb --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/moditect/module-info.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.binding.openapi.asyncapi +{ + requires org.leadpony.justify; + + requires io.aklivity.zilla.runtime.engine; + requires io.aklivity.zilla.runtime.binding.asyncapi; + requires io.aklivity.zilla.runtime.binding.openapi; + requires io.aklivity.zilla.runtime.binding.http; + requires io.aklivity.zilla.runtime.binding.kafka; + requires io.aklivity.zilla.runtime.binding.http.kafka; + + exports io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + + provides io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBindingFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncCompositeBindingAdapter; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiOptionsConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiConditionConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiWithConfigAdapter; +} diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi new file mode 100644 index 0000000000..2b0d43f82d --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBindingFactorySpi diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi new file mode 100644 index 0000000000..ccb4b52815 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncCompositeBindingAdapter diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi new file mode 100644 index 0000000000..346243b60f --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiConditionConfigAdapter diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..af68b6a234 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiOptionsConfigAdapter diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi new file mode 100644 index 0000000000..7b82979aff --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiWithConfigAdapter diff --git a/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapterTest.java b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapterTest.java new file mode 100644 index 0000000000..3dc345e468 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class OpenapiAsyncapiConditionConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new OpenapiAsyncapiConditionConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadCondition() + { + String text = + "{" + + "\"api-id\":\"test\"," + + "\"operation-id\":\"o-id\"" + + "}"; + + OpenapiAsyncapiConditionConfig condition = jsonb.fromJson(text, OpenapiAsyncapiConditionConfig.class); + + assertThat(condition, not(nullValue())); + assertThat(condition.apiId, equalTo("test")); + assertThat(condition.operationId, equalTo("o-id")); + } + + @Test + public void shouldWriteCondition() + { + OpenapiAsyncapiConditionConfig condition = new OpenapiAsyncapiConditionConfig("test", "o-id"); + + String text = jsonb.toJson(condition); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"api-id\":\"test\"," + + "\"operation-id\":\"o-id\"" + + "}")); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapterTest.java b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..9d24c5c86d --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapterTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.singleton; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiSpecConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.specs.binding.openapi.asyncapi.OpenapiAsyncapiSpecs; + +public class OpenapiAsyncapiOptionsConfigAdapterTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + + private Jsonb jsonb; + + @Before + public void initJson() throws IOException + { + initSpec("openapi/petstore.yaml"); + initSpec("asyncapi/petstore.yaml"); + } + + public void initSpec( + String specConfig) throws IOException + { + try (InputStream resource = OpenapiAsyncapiSpecs.class + .getResourceAsStream("config/" + specConfig)) + { + String content = new String(resource.readAllBytes(), UTF_8); + Mockito.doReturn(content).when(context).readURL(specConfig); + + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("openapi-asyncapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + } + + @Test + public void shouldReadOptions() + { + String text = + "{" + + " \"specs\": {" + + " \"openapi\": {" + + " \"openapi-id\": \"openapi/petstore.yaml\"" + + " }," + + " \"asyncapi\": {" + + " \"asyncapi-id\": \"asyncapi/petstore.yaml\"" + + " }" + + " }" + + "}"; + + OpenapiAsyncapiOptionsConfig options = jsonb.fromJson(text, OpenapiAsyncapiOptionsConfig.class); + assertThat(options, not(nullValue())); + OpenapiConfig openapi = options.specs.openapi.stream().findFirst().get(); + assertEquals("openapi-id", openapi.apiLabel); + assertThat(openapi.openapi, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.asyncapi.stream().findFirst().get(); + assertEquals("asyncapi-id", asyncapi.apiLabel); + assertThat(asyncapi.asyncapi, not(nullValue())); + } + + @Test + public void shouldWriteOptions() + { + String expected = "{\"specs\":{\"openapi\":{\"openapi-id\":\"openapi/petstore.yaml\"},\"asyncapi\":" + + "{\"asyncapi-id\":\"asyncapi/petstore.yaml\"}}}"; + + OpenapiConfig openapi = new OpenapiConfig("openapi-id", 0L, + "openapi/petstore.yaml", new Openapi()); + AsyncapiConfig asyncapi = new AsyncapiConfig("asyncapi-id", 0L, + "asyncapi/petstore.yaml", new Asyncapi()); + + final OpenapiAsyncapiOptionsConfig options = new OpenapiAsyncapiOptionsConfig( + new OpenapiAsyncapiSpecConfig(singleton(openapi), + singleton(asyncapi))); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertEquals(expected, text); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapterTest.java b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapterTest.java new file mode 100644 index 0000000000..f82eebf73a --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapterTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class OpenapiAsyncapiWithConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new OpenapiAsyncapiWithConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadWith() + { + String text = "{\"api-id\":\"test\",\"operation-id\":\"o-id\"}"; + + OpenapiAsyncapiWithConfig with = jsonb.fromJson(text, OpenapiAsyncapiWithConfig.class); + + assertThat(with, not(nullValue())); + assertThat(with.apiId, equalTo("test")); + assertThat(with.operationId, equalTo("o-id")); + } + + @Test + public void shouldWriteWith() + { + OpenapiAsyncapiWithConfig with = new OpenapiAsyncapiWithConfig("test", "o-id"); + + String text = jsonb.toJson(with); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo("{\"api-id\":\"test\",\"operation-id\":\"o-id\"}")); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java new file mode 100644 index 0000000000..2c28776b51 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class OpenapiAsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi") + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/asyncapi/config") + .external("asyncapi_client0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("proxy.yaml") + @Specification({ + "${openapi}/create.pet/client", + "${asyncapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-openapi.spec/LICENSE b/incubator/binding-openapi.spec/LICENSE index 1184b83429..90fe2ef74f 100644 --- a/incubator/binding-openapi.spec/LICENSE +++ b/incubator/binding-openapi.spec/LICENSE @@ -112,3 +112,4 @@ the entity on whose behalf you are receiving the Software. Agreement or the failure by Aklivity to exercise any right hereunder will not be construed as a waiver of any subsequent breach of that right or as a waiver of any other right. + diff --git a/incubator/binding-openapi.spec/NOTICE b/incubator/binding-openapi.spec/NOTICE index 3936d236bc..7b0a8539b0 100644 --- a/incubator/binding-openapi.spec/NOTICE +++ b/incubator/binding-openapi.spec/NOTICE @@ -1,15 +1,13 @@ -Licensed under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at: +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 -http://www.apache.org/licenses/LICENSE-2.0 + 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 OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. This project includes: agrona under The Apache License, Version 2.0 diff --git a/incubator/binding-openapi.spec/NOTICE.template b/incubator/binding-openapi.spec/NOTICE.template index e9ed8f0e7b..67b08ca131 100644 --- a/incubator/binding-openapi.spec/NOTICE.template +++ b/incubator/binding-openapi.spec/NOTICE.template @@ -1,18 +1,16 @@ -Licensed under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at: +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 -http://www.apache.org/licenses/LICENSE-2.0 + 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 OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. This project includes: #GENERATED_NOTICES# This project also includes code under copyright of the following entities: - https://github.com/reaktivity/ \ No newline at end of file + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi.spec/pom.xml b/incubator/binding-openapi.spec/pom.xml index d1a29c34c9..022e246172 100644 --- a/incubator/binding-openapi.spec/pom.xml +++ b/incubator/binding-openapi.spec/pom.xml @@ -26,7 +26,7 @@ 11 11 - 0.96 + 0.91 1 diff --git a/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java index 5dca05c51b..d06d407f29 100644 --- a/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java +++ b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java @@ -15,11 +15,17 @@ */ package io.aklivity.zilla.specs.binding.openapi; +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; +import org.kaazing.k3po.lang.el.BytesMatcher; import org.kaazing.k3po.lang.el.Function; import org.kaazing.k3po.lang.el.spi.FunctionMapperSpi; +import io.aklivity.zilla.specs.binding.openapi.internal.types.OctetsFW; import io.aklivity.zilla.specs.binding.openapi.internal.types.stream.OpenapiBeginExFW; public final class OpenapiFunctions @@ -30,6 +36,12 @@ public static OpenapiBeginExBuilder beginEx() return new OpenapiBeginExBuilder(); } + @Function + public static OpenapiBeginExMatcherBuilder matchBeginEx() + { + return new OpenapiBeginExMatcherBuilder(); + } + public static final class OpenapiBeginExBuilder { private final OpenapiBeginExFW.Builder beginExRW; @@ -47,6 +59,13 @@ public OpenapiBeginExBuilder typeId( return this; } + public OpenapiBeginExBuilder apiId( + long apiId) + { + beginExRW.apiId(apiId); + return this; + } + public OpenapiBeginExBuilder operationId( String operationId) { @@ -70,6 +89,102 @@ public byte[] build() } } + public static final class OpenapiBeginExMatcherBuilder + { + private final DirectBuffer bufferRO = new UnsafeBuffer(); + + private final OpenapiBeginExFW beginExRO = new OpenapiBeginExFW(); + + private Integer typeId; + private Long apiId; + private String operationId; + private OctetsFW.Builder extensionRW; + + public OpenapiBeginExMatcherBuilder typeId( + int typeId) + { + this.typeId = typeId; + return this; + } + + public OpenapiBeginExMatcherBuilder operationId( + String operationId) + { + this.operationId = operationId; + return this; + } + + public OpenapiBeginExMatcherBuilder apiId( + long apiId) + { + this.apiId = apiId; + return this; + } + + public OpenapiBeginExMatcherBuilder extension( + byte[] extension) + { + assert extensionRW == null; + extensionRW = new OctetsFW.Builder().wrap(new UnsafeBuffer(new byte[1024]), 0, 1024); + + extensionRW.set(Objects.requireNonNullElseGet(extension, () -> new byte[] {})); + + return this; + } + + public BytesMatcher build() + { + return typeId != null ? this::match : buf -> null; + } + + private OpenapiBeginExFW match( + ByteBuffer byteBuf) throws Exception + { + if (!byteBuf.hasRemaining()) + { + return null; + } + + bufferRO.wrap(byteBuf); + final OpenapiBeginExFW beginEx = beginExRO.tryWrap(bufferRO, byteBuf.position(), byteBuf.limit()); + + if (beginEx != null && + matchTypeId(beginEx) && + matchApiId(beginEx) && + matchOperationId(beginEx) && + matchExtension(beginEx)) + { + byteBuf.position(byteBuf.position() + beginEx.sizeof()); + return beginEx; + } + throw new Exception(beginEx.toString()); + } + + private boolean matchTypeId( + OpenapiBeginExFW beginEx) + { + return typeId == beginEx.typeId(); + } + + private boolean matchApiId( + OpenapiBeginExFW beginEx) + { + return apiId == null || apiId == beginEx.apiId(); + } + + private boolean matchOperationId( + OpenapiBeginExFW beginEx) + { + return operationId == null || operationId.equals(beginEx.operationId().asString()); + } + + private boolean matchExtension( + final OpenapiBeginExFW beginEx) + { + return extensionRW == null || extensionRW.build().equals(beginEx.extension()); + } + } + public static class Mapper extends FunctionMapperSpi.Reflective { public Mapper() diff --git a/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi b/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi index cb5c82fd24..3504330305 100644 --- a/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi +++ b/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi @@ -1 +1,2 @@ io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions$Mapper + diff --git a/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl b/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl index 5a24b610ff..8b845e5025 100644 --- a/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl +++ b/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl @@ -20,7 +20,7 @@ scope openapi { struct OpenapiBeginEx extends core::stream::Extension { - int32 apiId = 0; + int64 apiId = 0; string16 operationId = null; octets extension; } diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml index 954302a886..fe9cddc73b 100644 --- a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml @@ -25,4 +25,4 @@ bindings: host: localhost port: 8080 specs: - - openapi/petstore.yaml + openapi-id: openapi/petstore.yaml diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml index 9dd4d71306..0fe20d21c1 100644 --- a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml @@ -41,5 +41,5 @@ bindings: query: access_token: "{credentials}" specs: - - openapi/petstore.yaml + openapi-id: openapi/petstore.yaml exit: openapi0 diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml index ce6e1fc0ee..07ad11ac12 100644 --- a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml @@ -22,5 +22,5 @@ bindings: kind: server options: specs: - - openapi/petstore.yaml + openapi-id: openapi/petstore.yaml exit: openapi0 diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json index 8f7161754c..4ab6505428 100644 --- a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json @@ -50,12 +50,15 @@ "specs": { "title": "Specs", - "type": "array", - "items": + "type": "object", + "patternProperties": { - "title": "Specs", - "type": "string" - } + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "type": "string" + } + }, + "maxProperties": 1 } }, "additionalProperties": false diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt index 11a25bae39..11cb08ae8b 100644 --- a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt @@ -36,7 +36,7 @@ connected write "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" write close -read zilla:begin.ext ${openapi:beginEx() +read zilla:begin.ext ${openapi:matchBeginEx() .typeId(zilla:id("openapi")) .operationId("createPets") .extension(http:beginEx() diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt index c074450d95..2ad7614964 100644 --- a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt @@ -19,7 +19,7 @@ accept "zilla://streams/openapi0" option zilla:transmission "half-duplex" accepted -read zilla:begin.ext ${openapi:beginEx() +read zilla:begin.ext ${openapi:matchBeginEx() .typeId(zilla:id("openapi")) .operationId("createPets") .extension(http:beginEx() diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java index ef2ffec766..8624a3c0ee 100644 --- a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java @@ -20,19 +20,22 @@ import static org.junit.Assert.assertSame; import java.lang.reflect.Method; +import java.nio.ByteBuffer; import javax.el.ELContext; import javax.el.FunctionMapper; import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; import org.junit.Test; +import org.kaazing.k3po.lang.el.BytesMatcher; import org.kaazing.k3po.lang.internal.el.ExpressionContext; import io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions; +import io.aklivity.zilla.specs.binding.openapi.internal.types.OctetsFW; import io.aklivity.zilla.specs.binding.openapi.internal.types.stream.OpenapiBeginExFW; - public class OpenapiFunctionsTest { @Test @@ -51,6 +54,7 @@ public void shouldGenerateBeginExtension() { byte[] build = OpenapiFunctions.beginEx() .typeId(0x01) + .apiId(1L) .operationId("test") .extension("extension".getBytes()) .build(); @@ -58,8 +62,31 @@ public void shouldGenerateBeginExtension() DirectBuffer buffer = new UnsafeBuffer(build); OpenapiBeginExFW beginEx = new OpenapiBeginExFW().wrap(buffer, 0, buffer.capacity()); - assertEquals(0L, beginEx.apiId()); + assertEquals(1L, beginEx.apiId()); assertEquals("test", beginEx.operationId().asString()); assertEquals("extension".length(), beginEx.extension().sizeof()); } + + @Test + public void shouldMatchOpenapiBeginExtension() throws Exception + { + BytesMatcher matcher = OpenapiFunctions.matchBeginEx() + .typeId(0x00) + .apiId(1) + .operationId("operationId") + .extension(new byte[] {1}) + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(26); + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + + new OpenapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x00) + .apiId(1) + .operationId("operationId") + .extension(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } } diff --git a/incubator/binding-openapi/NOTICE.template b/incubator/binding-openapi/NOTICE.template index 209ca12f74..67b08ca131 100644 --- a/incubator/binding-openapi/NOTICE.template +++ b/incubator/binding-openapi/NOTICE.template @@ -11,3 +11,6 @@ specific language governing permissions and limitations under the License. This project includes: #GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java index dd2239e5e8..7b9148d492 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java @@ -14,17 +14,24 @@ */ package io.aklivity.zilla.runtime.binding.openapi.config; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; public class OpenapiConfig { + public final String apiLabel; + public final long apiId; public final String location; - public final OpenApi openapi; + public final Openapi openapi; public OpenapiConfig( + String apiLabel, + long apiId, String location, - OpenApi openapi) + Openapi openapi) { + this.apiLabel = apiLabel; + this.apiId = apiId; this.location = location; this.openapi = openapi; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiParser.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiParser.java new file mode 100644 index 0000000000..1adda6259d --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiParser.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import static java.util.Collections.unmodifiableMap; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.InputStream; +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import org.agrona.collections.Object2ObjectHashMap; +import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.JsonValidationService; +import org.leadpony.justify.api.ProblemHandler; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.engine.config.ConfigException; + +public class OpenapiParser +{ + private final Map schemas; + + public OpenapiParser() + { + Map schemas = new Object2ObjectHashMap<>(); + schemas.put("3.0.0", schema("3.0.0")); + schemas.put("3.1.0", schema("3.1.0")); + this.schemas = unmodifiableMap(schemas); + } + + public Openapi parse( + String openapiText) + { + Openapi openApi = null; + + List errors = new LinkedList<>(); + + try + { + String openApiVersion = detectOpenApiVersion(openapiText); + + JsonValidationService service = JsonValidationService.newInstance(); + ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); + JsonSchema schema = schemas.get(openApiVersion); + + service.createReader(new StringReader(openapiText), schema, handler).read(); + + Jsonb jsonb = JsonbBuilder.create(); + + openApi = jsonb.fromJson(openapiText, Openapi.class); + } + catch (Exception ex) + { + errors.add(ex); + } + + if (!errors.isEmpty()) + { + Exception ex = errors.remove(0); + errors.forEach(ex::addSuppressed); + rethrowUnchecked(ex); + } + + return openApi; + } + + private JsonSchema schema( + String version) + { + InputStream schemaInput = null; + boolean detect = true; + + if (version.startsWith("3.0")) + { + schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.0.schema.json"); + } + else if (version.startsWith("3.1")) + { + schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.1.schema.json"); + detect = false; + } + + JsonValidationService service = JsonValidationService.newInstance(); + + return service.createSchemaReaderFactoryBuilder() + .withSpecVersionDetection(detect) + .build() + .createSchemaReader(schemaInput) + .read(); + } + + private String detectOpenApiVersion( + String openapiText) + { + try (JsonReader reader = Json.createReader(new StringReader(openapiText))) + { + JsonObject json = reader.readObject(); + if (json.containsKey("openapi")) + { + return json.getString("openapi"); + } + else + { + throw new IllegalArgumentException("Unable to determine OpenAPI version."); + } + } + catch (Exception e) + { + throw new RuntimeException("Error reading OpenAPI document.", e); + } + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java index 58c5fae230..2ece085095 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java @@ -36,7 +36,7 @@ import org.agrona.collections.Object2ObjectHashMap; import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; import io.aklivity.zilla.runtime.binding.openapi.internal.types.HttpHeaderFW; import io.aklivity.zilla.runtime.binding.openapi.internal.types.String16FW; import io.aklivity.zilla.runtime.binding.openapi.internal.types.String8FW; @@ -57,8 +57,8 @@ public final class OpenapiBindingConfig private final long overrideRouteId; private final IntHashSet httpOrigins; private final Long2LongHashMap resolvedIds; - private final Object2ObjectHashMap paths; - private final Map> resolversByMethod; + private final Object2ObjectHashMap paths; + private final Map> resolversByMethod; public OpenapiBindingConfig( BindingConfig binding, @@ -86,7 +86,7 @@ public OpenapiBindingConfig( .filter(b -> b.type.equals("http")) .collect(of( () -> new Long2LongHashMap(-1), - (m, r) -> m.put(0L, r.id), //TODO: populate proper apiId + (m, r) -> m.put(options.openapis.stream().findFirst().get().apiId, r.id), (m, r) -> m, IDENTITY_FINISH )); @@ -99,7 +99,7 @@ public OpenapiBindingConfig( .collect(toCollection(IntHashSet::new)); this.helper = new HttpHeaderHelper(); - Map> resolversByMethod = new TreeMap<>(CharSequence::compare); + Map> resolversByMethod = new TreeMap<>(CharSequence::compare); resolversByMethod.put("POST", o -> o.post != null ? o.post.operationId : null); resolversByMethod.put("PUT", o -> o.put != null ? o.put.operationId : null); resolversByMethod.put("GET", o -> o.get != null ? o.get.operationId : null); @@ -131,13 +131,13 @@ public String resolveOperationId( String operationId = null; resolve: - for (Map.Entry item : paths.entrySet()) + for (Map.Entry item : paths.entrySet()) { Matcher matcher = item.getKey(); matcher.reset(helper.path); if (matcher.find()) { - PathItem operations = item.getValue(); + OpenapiPathItem operations = item.getValue(); operationId = resolveMethod(operations); break resolve; } @@ -147,9 +147,9 @@ public String resolveOperationId( } private String resolveMethod( - PathItem operations) + OpenapiPathItem operations) { - Function resolver = resolversByMethod.get(helper.method); + Function resolver = resolversByMethod.get(helper.method); return resolver != null ? resolver.apply(operations) : null; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java index 7d1f47c21b..2318346547 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java @@ -28,11 +28,11 @@ import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiHeader; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiResponse; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.ResponseByContentType; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponseByContentType; import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiOperationView; import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiOperationsView; import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; @@ -74,7 +74,7 @@ public BindingConfig adapt( OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options; OpenapiConfig openapiConfig = options.openapis.get(0); - final OpenApi openApi = openapiConfig.openapi; + final Openapi openApi = openapiConfig.openapi; final int[] httpsPorts = resolvePortsForScheme(openApi, "https"); final boolean secure = httpsPorts != null; @@ -100,7 +100,7 @@ public BindingConfig adapt( } private int[] resolvePortsForScheme( - OpenApi openApi, + Openapi openApi, String scheme) { requireNonNull(scheme); @@ -114,7 +114,7 @@ private int[] resolvePortsForScheme( } private URI findFirstServerUrlWithScheme( - OpenApi openApi, + Openapi openApi, String scheme) { requireNonNull(scheme); @@ -133,7 +133,7 @@ private URI findFirstServerUrlWithScheme( private BindingConfigBuilder injectHttpClientOptions( BindingConfigBuilder binding, - OpenApi openApi) + Openapi openApi) { OpenApiOperationsView operations = OpenApiOperationsView.of(openApi.paths); if (operations.hasResponses()) @@ -149,7 +149,7 @@ private BindingConfigBuilder injectHttpClientOptions( private HttpOptionsConfigBuilder injectHttpClientRequests( OpenApiOperationsView operations, HttpOptionsConfigBuilder options, - OpenApi openApi) + Openapi openApi) { for (String pathName : openApi.paths.keySet()) { @@ -175,14 +175,14 @@ private HttpOptionsConfigBuilder injectHttpClientRequests( private HttpRequestConfigBuilder injectResponses( HttpRequestConfigBuilder request, OpenApiOperationView operation, - OpenApi openApi) + Openapi openApi) { if (operation != null && operation.responsesByStatus() != null) { - for (Map.Entry responses0 : operation.responsesByStatus().entrySet()) + for (Map.Entry responses0 : operation.responsesByStatus().entrySet()) { String status = responses0.getKey(); - ResponseByContentType responses1 = responses0.getValue(); + OpenapiResponseByContentType responses1 = responses0.getValue(); if (!(OpenApiOperationView.DEFAULT.equals(status)) && responses1.content != null) { for (Map.Entry response2 : responses1.content.entrySet()) @@ -210,7 +210,7 @@ private HttpRequestConfigBuilder injectResponses( } private HttpResponseConfigBuilder injectResponseHeaders( - ResponseByContentType responses, + OpenapiResponseByContentType responses, HttpResponseConfigBuilder response) { if (responses.headers != null && !responses.headers.isEmpty()) diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java index c967db887f..caf59177df 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java @@ -14,42 +14,26 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.config; -import static java.util.Collections.unmodifiableMap; -import static org.agrona.LangUtil.rethrowUnchecked; +import static java.nio.charset.StandardCharsets.UTF_8; -import java.io.InputStream; -import java.io.StringReader; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; import java.util.function.Function; +import java.util.zip.CRC32C; import jakarta.json.Json; -import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonReader; import jakarta.json.JsonString; -import jakarta.json.JsonValue; -import jakarta.json.bind.Jsonb; -import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.adapter.JsonbAdapter; -import org.agrona.collections.Object2ObjectHashMap; -import org.leadpony.justify.api.JsonSchema; -import org.leadpony.justify.api.JsonValidationService; -import org.leadpony.justify.api.ProblemHandler; - import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiParser; import io.aklivity.zilla.runtime.binding.openapi.config.OpenpaiOptionsConfigBuilder; import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; -import io.aklivity.zilla.runtime.engine.config.ConfigException; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; @@ -61,7 +45,8 @@ public final class OpenapiOptionsConfigAdapter implements OptionsConfigAdapterSp private static final String HTTP_NAME = "http"; private static final String SPECS_NAME = "specs"; - private final Map schemas; + private final OpenapiParser parser; + private final CRC32C crc; private OptionsConfigAdapter tcpOptions; private OptionsConfigAdapter tlsOptions; @@ -70,10 +55,8 @@ public final class OpenapiOptionsConfigAdapter implements OptionsConfigAdapterSp public OpenapiOptionsConfigAdapter() { - Map schemas = new Object2ObjectHashMap<>(); - schemas.put("3.0.0", schema("3.0.0")); - schemas.put("3.1.0", schema("3.1.0")); - this.schemas = unmodifiableMap(schemas); + this.parser = new OpenapiParser(); + this.crc = new CRC32C(); } @Override @@ -92,33 +75,33 @@ public String type() public JsonObject adaptToJson( OptionsConfig options) { - OpenapiOptionsConfig openOptions = (OpenapiOptionsConfig) options; + OpenapiOptionsConfig openapiOptions = (OpenapiOptionsConfig) options; JsonObjectBuilder object = Json.createObjectBuilder(); - if (openOptions.tcp != null) + if (openapiOptions.tcp != null) { final TcpOptionsConfig tcp = ((OpenapiOptionsConfig) options).tcp; object.add(TCP_NAME, tcpOptions.adaptToJson(tcp)); } - if (openOptions.tls != null) + if (openapiOptions.tls != null) { final TlsOptionsConfig tls = ((OpenapiOptionsConfig) options).tls; object.add(TLS_NAME, tlsOptions.adaptToJson(tls)); } - HttpOptionsConfig http = openOptions.http; + HttpOptionsConfig http = openapiOptions.http; if (http != null) { object.add(HTTP_NAME, httpOptions.adaptToJson(http)); } - if (openOptions.openapis != null) + if (openapiOptions.openapis != null) { - JsonArrayBuilder keys = Json.createArrayBuilder(); - openOptions.openapis.forEach(p -> keys.add(p.location)); - object.add(SPECS_NAME, keys); + JsonObjectBuilder openapi = Json.createObjectBuilder(); + openapiOptions.openapis.forEach(o -> openapi.add(o.apiLabel, o.location)); + object.add(SPECS_NAME, openapi); } return object.build(); @@ -154,7 +137,17 @@ public OptionsConfig adaptFromJson( if (object.containsKey(SPECS_NAME)) { - object.getJsonArray(SPECS_NAME).forEach(s -> openapiOptions.openapi(asOpenapi(s))); + JsonObject openapi = object.getJsonObject(SPECS_NAME); + openapi.forEach((n, v) -> + { + final String location = JsonString.class.cast(v).getString(); + final String specText = readURL.apply(location); + final String apiLabel = n; + crc.reset(); + crc.update(specText.getBytes(UTF_8)); + final long apiId = crc.getValue(); + openapiOptions.openapi(new OpenapiConfig(apiLabel, apiId, location, parser.parse(specText))); + }); } return openapiOptions.build(); @@ -172,96 +165,4 @@ public void adaptContext( this.httpOptions = new OptionsConfigAdapter(Kind.BINDING, context); this.httpOptions.adaptType("http"); } - - private OpenapiConfig asOpenapi( - JsonValue value) - { - final String location = ((JsonString) value).getString(); - final String specText = readURL.apply(location); - OpenApi openapi = parseOpenApi(specText); - - return new OpenapiConfig(location, openapi); - } - - private OpenApi parseOpenApi( - String openapiText) - { - OpenApi openApi = null; - - List errors = new LinkedList<>(); - - try - { - String openApiVersion = detectOpenApiVersion(openapiText); - - JsonValidationService service = JsonValidationService.newInstance(); - ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); - JsonSchema schema = schemas.get(openApiVersion); - - service.createReader(new StringReader(openapiText), schema, handler).read(); - - Jsonb jsonb = JsonbBuilder.create(); - - openApi = jsonb.fromJson(openapiText, OpenApi.class); - } - catch (Exception ex) - { - errors.add(ex); - } - - if (!errors.isEmpty()) - { - Exception ex = errors.remove(0); - errors.forEach(ex::addSuppressed); - rethrowUnchecked(ex); - } - - return openApi; - } - - private String detectOpenApiVersion( - String openapiText) - { - try (JsonReader reader = Json.createReader(new StringReader(openapiText))) - { - JsonObject json = reader.readObject(); - if (json.containsKey("openapi")) - { - return json.getString("openapi"); - } - else - { - throw new IllegalArgumentException("Unable to determine OpenAPI version."); - } - } - catch (Exception e) - { - throw new RuntimeException("Error reading OpenAPI document.", e); - } - } - - private JsonSchema schema( - String version) - { - InputStream schemaInput = null; - boolean detect = true; - - if (version.startsWith("3.0")) - { - schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.0.schema.json"); - } - else if (version.startsWith("3.1")) - { - schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.1.schema.json"); - detect = false; - } - - JsonValidationService service = JsonValidationService.newInstance(); - - return service.createSchemaReaderFactoryBuilder() - .withSpecVersionDetection(detect) - .build() - .createSchemaReader(schemaInput) - .read(); - } } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java index 1cc86feda2..efa232860b 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java @@ -39,12 +39,12 @@ import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiMediaType; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiParameter; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiSchema; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiSchemaView; import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiServerView; @@ -90,7 +90,7 @@ public BindingConfig adapt( OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options; OpenapiConfig openapiConfig = options.openapis.get(0); - final OpenApi openApi = openapiConfig.openapi; + final Openapi openApi = openapiConfig.openapi; final TlsOptionsConfig tlsOption = options.tls != null ? options.tls : null; final HttpOptionsConfig httpOptions = options.http; final String guardName = httpOptions != null ? httpOptions.authorization.name : null; @@ -141,7 +141,7 @@ private BindingConfigBuilder injectPlainTcpRoute( int[] httpPorts, boolean secure) { - if (secure) + if (!secure) { binding .route() @@ -206,7 +206,7 @@ private HttpOptionsConfigBuilder injectHttpServerOptions( private HttpOptionsConfigBuilder injectHttpServerRequests( HttpOptionsConfigBuilder options, - OpenApi openApi) + Openapi openApi) { for (String pathName : openApi.paths.keySet()) { @@ -220,7 +220,6 @@ private HttpOptionsConfigBuilder injectHttpServerRequests( .request() .path(pathName) .method(HttpRequestConfig.Method.valueOf(methodName)) - .inject(request -> injectContent(request, operation, openApi)) .inject(request -> injectParams(request, operation)) .build(); } @@ -232,7 +231,7 @@ private HttpOptionsConfigBuilder injectHttpServerRequests( private HttpRequestConfigBuilder injectContent( HttpRequestConfigBuilder request, OpenApiOperation operation, - OpenApi openApi) + Openapi openApi) { if (operation.requestBody != null && operation.requestBody.content != null && !operation.requestBody.content.isEmpty()) { @@ -299,7 +298,7 @@ private HttpRequestConfigBuilder injectParams( private BindingConfigBuilder injectHttpServerRoutes( BindingConfigBuilder binding, - OpenApi openApi, + Openapi openApi, String qname, String guardName, Map securitySchemes) @@ -366,7 +365,7 @@ private GuardedConfigBuilder injectGuardedRoles( private NamespaceConfigBuilder injectCatalog( NamespaceConfigBuilder namespace, - OpenApi openApi) + Openapi openApi) { if (openApi.components != null && openApi.components.schemas != null && @@ -388,7 +387,7 @@ private NamespaceConfigBuilder injectCatalog( private InlineSchemaConfigBuilder injectSubjects( InlineSchemaConfigBuilder subjects, - OpenApi openApi) + Openapi openApi) { try (Jsonb jsonb = JsonbBuilder.create()) { @@ -409,7 +408,7 @@ private InlineSchemaConfigBuilder injectSubjects( } private int[] resolveAllPorts( - OpenApi openApi) + Openapi openApi) { int[] ports = new int[openApi.servers.size()]; for (int i = 0; i < openApi.servers.size(); i++) @@ -422,7 +421,7 @@ private int[] resolveAllPorts( } private int[] resolvePortsForScheme( - OpenApi openApi, + Openapi openApi, String scheme) { requireNonNull(scheme); @@ -436,7 +435,7 @@ private int[] resolvePortsForScheme( } private URI findFirstServerUrlWithScheme( - OpenApi openApi, + Openapi openApi, String scheme) { requireNonNull(scheme); @@ -454,7 +453,7 @@ private URI findFirstServerUrlWithScheme( } private Map resolveSecuritySchemes( - OpenApi openApi) + Openapi openApi) { requireNonNull(openApi); Map result = new Object2ObjectHashMap<>(); @@ -475,7 +474,7 @@ private Map resolveSecuritySchemes( private OpenApiSchemaView resolveSchemaForJsonContentType( Map content, - OpenApi openApi) + Openapi openApi) { OpenApiMediaType mediaType = null; if (content != null) diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java index fe9aca2858..b6e69f0408 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java @@ -23,5 +23,5 @@ public class OpenApiOperation public String operationId; public OpenApiRequestBody requestBody; public List parameters; - public Map responses; + public Map responses; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java similarity index 92% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java index d6c6404a31..45a5a566bf 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApi.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java @@ -17,10 +17,10 @@ import java.util.List; import java.util.Map; -public class OpenApi +public class Openapi { public String openapi; public List servers; - public Map paths; + public Map paths; public OpenApiComponents components; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java similarity index 97% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java index 64a976faa4..8eddf9c820 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/PathItem.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java @@ -14,7 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class PathItem +public class OpenapiPathItem { public OpenApiOperation get; public OpenApiOperation put; diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java similarity index 94% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java index 280e28eaee..c79664532b 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/ResponseByContentType.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java @@ -16,7 +16,7 @@ import java.util.Map; -public class ResponseByContentType +public class OpenapiResponseByContentType { public Map headers; public Map content; diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java index 95b9ae0233..05c9c7891f 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java @@ -154,6 +154,7 @@ public MessageConsumer newStream( if (route != null) { + final long apiId = binding.options.openapis.get(0).apiId; final String operationId = binding.resolveOperationId(httpBeginEx); newStream = new HttpStream( @@ -164,6 +165,7 @@ public MessageConsumer newStream( affinity, authorization, route.id, + apiId, operationId)::onHttpMessage; } @@ -204,9 +206,10 @@ private HttpStream( long affinity, long authorization, long resolvedId, + long apiId, String operationId) { - this.openapi = new OpenapiStream(this, routedId, resolvedId, authorization, operationId); + this.openapi = new OpenapiStream(this, routedId, resolvedId, authorization, apiId, operationId); this.sender = sender; this.originId = originId; this.routedId = routedId; @@ -507,6 +510,7 @@ private final class OpenapiStream private final String operationId; private final long originId; private final long routedId; + private final long apiId; private final long authorization; private final long initialId; @@ -530,11 +534,13 @@ private OpenapiStream( long originId, long routedId, long authorization, + long apiId, String operationId) { this.delegate = delegate; this.originId = originId; this.routedId = routedId; + this.apiId = apiId; this.receiver = MessageConsumer.NOOP; this.authorization = authorization; this.initialId = supplyInitialId.applyAsLong(routedId); @@ -730,6 +736,7 @@ private void doOpenapiBegin( final OpenapiBeginExFW openapiBeginEx = openapiBeginExRW .wrap(extBuffer, 0, extBuffer.capacity()) .typeId(openapiTypeId) + .apiId(apiId) .operationId(operationId) .extension(extension) .build(); diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java index 4a5baa320b..45123ef155 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java @@ -17,7 +17,7 @@ import java.util.Map; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.ResponseByContentType; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponseByContentType; public final class OpenApiOperationView { @@ -33,7 +33,7 @@ private OpenApiOperationView( this.hasResponses = initHasResponses(); } - public Map responsesByStatus() + public Map responsesByStatus() { return operation.responses; } @@ -48,10 +48,10 @@ private boolean initHasResponses() boolean result = false; if (operation != null && operation.responses != null) { - for (Map.Entry response0 : operation.responses.entrySet()) + for (Map.Entry response0 : operation.responses.entrySet()) { String status = response0.getKey(); - ResponseByContentType response1 = response0.getValue(); + OpenapiResponseByContentType response1 = response0.getValue(); if (!(DEFAULT.equals(status)) && response1.content != null) { result = true; diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java index 63265476d5..d2527e84d1 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java @@ -19,7 +19,7 @@ import org.agrona.collections.Object2ObjectHashMap; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; public final class OpenApiOperationsView { @@ -39,13 +39,13 @@ public OpenApiOperationView operation( } public static OpenApiOperationsView of( - Map paths) + Map paths) { return new OpenApiOperationsView(paths); } private OpenApiOperationsView( - Map paths) + Map paths) { this.operationsByPath = new Object2ObjectHashMap<>(); boolean hasResponses = false; diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java index 4e80c12958..3fbee83d26 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java @@ -20,7 +20,7 @@ import java.util.Map; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; public final class OpenApiPathView { @@ -32,13 +32,13 @@ public Map methods() } public static OpenApiPathView of( - PathItem pathItem) + OpenapiPathItem pathItem) { return new OpenApiPathView(pathItem); } private OpenApiPathView( - PathItem pathItem) + OpenapiPathItem pathItem) { Map methods = new LinkedHashMap<>(); putIfNotNull(methods, "GET", pathItem.get); diff --git a/incubator/binding-openapi/src/main/moditect/module-info.java b/incubator/binding-openapi/src/main/moditect/module-info.java index 483f8145bd..9e8001e5dd 100644 --- a/incubator/binding-openapi/src/main/moditect/module-info.java +++ b/incubator/binding-openapi/src/main/moditect/module-info.java @@ -29,6 +29,7 @@ exports io.aklivity.zilla.runtime.binding.openapi.config; opens io.aklivity.zilla.runtime.binding.openapi.internal.model; + opens io.aklivity.zilla.runtime.binding.openapi.internal.view; provides io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi with io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBindingFactorySpi; diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java index 5acbad38d6..d67df631e7 100644 --- a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java @@ -39,8 +39,8 @@ import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApi; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.PathItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; @@ -102,14 +102,14 @@ public void shouldReadOptions() " }" + " }" + " }," + - " \"specs\": [" + - " \"openapi/petstore.yaml\"" + - " ]" + + " \"specs\": {" + + " \"openapi-id\": \"openapi/petstore.yaml\"" + + " }" + " }"; OpenapiOptionsConfig options = jsonb.fromJson(text, OpenapiOptionsConfig.class); OpenapiConfig openapi = options.openapis.stream().findFirst().get(); - PathItem path = openapi.openapi.paths.get("/pets"); + OpenapiPathItem path = openapi.openapi.paths.get("/pets"); assertThat(options, not(nullValue())); assertThat(path.post, not(nullValue())); @@ -121,7 +121,7 @@ public void shouldReadOptions() public void shouldWriteOptions() { String expected = "{\"tcp\":{\"host\":\"localhost\",\"port\":8080},\"tls\":{\"sni\":[\"example.net\"]}," + - "\"specs\":[\"openapi/petstore.yaml\"]}"; + "\"specs\":{\"openapi-id\":\"openapi/petstore.yaml\"}}"; TcpOptionsConfig tcp = TcpOptionsConfig.builder() .inject(identity()) @@ -135,7 +135,7 @@ public void shouldWriteOptions() .build(); OpenapiOptionsConfig options = new OpenapiOptionsConfig(tcp, tls, null, asList( - new OpenapiConfig("openapi/petstore.yaml", new OpenApi()))); + new OpenapiConfig("openapi-id", 1L, "openapi/petstore.yaml", new Openapi()))); String text = jsonb.toJson(options); diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java index d50830204d..6fc18b1176 100644 --- a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java @@ -77,5 +77,4 @@ public void shouldRejectNonCompositeOrigin() throws Exception { k3po.finish(); } - } diff --git a/incubator/pom.xml b/incubator/pom.xml index f84010afd5..806db5cc6f 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -20,6 +20,7 @@ binding-amqp.spec binding-asyncapi.spec binding-openapi.spec + binding-openapi-asyncapi.spec catalog-inline.spec catalog-schema-registry.spec exporter-otlp.spec @@ -32,6 +33,7 @@ binding-amqp binding-asyncapi binding-openapi + binding-openapi-asyncapi catalog-inline catalog-schema-registry @@ -67,6 +69,11 @@ binding-openapi ${project.version} + + ${project.groupId} + binding-openapi-asyncapi + ${project.version} + ${project.groupId} catalog-inline diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfig.java index 18a69eddde..03e590e2a8 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfig.java @@ -14,6 +14,8 @@ */ package io.aklivity.zilla.runtime.binding.http.kafka.config; +import java.util.function.Function; + import io.aklivity.zilla.runtime.engine.config.ConditionConfig; public final class HttpKafkaConditionConfig extends ConditionConfig @@ -28,4 +30,15 @@ public HttpKafkaConditionConfig( this.method = method; this.path = path; } + + public static HttpKafkaConditionConfigBuilder builder() + { + return new HttpKafkaConditionConfigBuilder<>(HttpKafkaConditionConfig.class::cast); + } + + public static HttpKafkaConditionConfigBuilder builder( + Function mapper) + { + return new HttpKafkaConditionConfigBuilder<>(mapper); + } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfigBuilder.java new file mode 100644 index 0000000000..3e8978424f --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfigBuilder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaConditionConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private String method; + private String path; + + public HttpKafkaConditionConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaConditionConfigBuilder method( + String method) + { + this.method = method; + return this; + } + + public HttpKafkaConditionConfigBuilder path( + String path) + { + this.path = path; + return this; + } + + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaConditionConfig(method, path)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfig.java similarity index 71% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfig.java rename to runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfig.java index b49368d8a6..a12a8896ab 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfig.java @@ -12,10 +12,12 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.http.kafka.config; import java.util.Optional; +import java.util.function.Function; +import io.aklivity.zilla.runtime.binding.http.kafka.internal.config.HttpKafkaCapability; import io.aklivity.zilla.runtime.engine.config.WithConfig; public final class HttpKafkaWithConfig extends WithConfig @@ -24,19 +26,18 @@ public final class HttpKafkaWithConfig extends WithConfig public final Optional fetch; public final Optional produce; - public HttpKafkaWithConfig( - HttpKafkaWithFetchConfig fetch) + public static HttpKafkaWithConfigBuilder builder() { - this(HttpKafkaCapability.FETCH, fetch, null); + return new HttpKafkaWithConfigBuilder<>(HttpKafkaWithConfig.class::cast); } - public HttpKafkaWithConfig( - HttpKafkaWithProduceConfig produce) + public static HttpKafkaWithConfigBuilder builder( + Function mapper) { - this(HttpKafkaCapability.PRODUCE, null, produce); + return new HttpKafkaWithConfigBuilder<>(mapper); } - private HttpKafkaWithConfig( + public HttpKafkaWithConfig( HttpKafkaCapability capability, HttpKafkaWithFetchConfig fetch, HttpKafkaWithProduceConfig produce) diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfigBuilder.java new file mode 100644 index 0000000000..8f710170c3 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfigBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.kafka.internal.config.HttpKafkaCapability; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.WithConfig; + +public final class HttpKafkaWithConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private HttpKafkaCapability capability; + private HttpKafkaWithFetchConfig fetch; + private HttpKafkaWithProduceConfig produce; + + HttpKafkaWithConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithConfigBuilder fetch( + HttpKafkaWithFetchConfig fetch) + { + capability = HttpKafkaCapability.FETCH; + this.fetch = fetch; + return this; + } + + public HttpKafkaWithConfigBuilder produce( + HttpKafkaWithProduceConfig produce) + { + capability = HttpKafkaCapability.PRODUCE; + this.produce = produce; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithConfig(capability, fetch, produce)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfig.java similarity index 66% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfig.java rename to runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfig.java index 29538da6c7..7c201c8c0a 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfig.java @@ -12,10 +12,13 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.http.kafka.config; import java.util.List; import java.util.Optional; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.kafka.internal.config.HttpKafkaWithFetchFilterConfig; public final class HttpKafkaWithFetchConfig { @@ -32,4 +35,16 @@ public HttpKafkaWithFetchConfig( this.filters = Optional.ofNullable(filters); this.merge = Optional.ofNullable(merged); } + + public static HttpKafkaWithFetchConfigBuilder builder() + { + return new HttpKafkaWithFetchConfigBuilder<>(HttpKafkaWithFetchConfig.class::cast); + } + + public static HttpKafkaWithFetchConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithFetchConfigBuilder<>(mapper); + } + } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfigBuilder.java new file mode 100644 index 0000000000..7edaec2ba0 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfigBuilder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.kafka.internal.config.HttpKafkaWithFetchFilterConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithFetchConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String topic; + private List filters; + private HttpKafkaWithFetchMergeConfig merge; + + HttpKafkaWithFetchConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithFetchConfigBuilder topic( + String topic) + { + this.topic = topic; + return this; + } + + public HttpKafkaWithFetchConfigBuilder filters( + List filters) + { + this.filters = filters; + return this; + } + + public HttpKafkaWithFetchConfigBuilder merged( + HttpKafkaWithFetchMergeConfig merge) + { + this.merge = merge; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithFetchConfig(topic, filters, merge)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchMergeConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfig.java similarity index 65% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchMergeConfig.java rename to runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfig.java index 40c42f1123..26a426a5d0 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchMergeConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfig.java @@ -12,7 +12,9 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; public final class HttpKafkaWithFetchMergeConfig { @@ -29,4 +31,15 @@ public HttpKafkaWithFetchMergeConfig( this.initial = initial; this.path = path; } + + public static HttpKafkaWithFetchMergeConfigBuilder builder() + { + return new HttpKafkaWithFetchMergeConfigBuilder<>(HttpKafkaWithFetchMergeConfig.class::cast); + } + + public static HttpKafkaWithFetchMergeConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithFetchMergeConfigBuilder<>(mapper); + } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfigBuilder.java new file mode 100644 index 0000000000..1b59132a2b --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfigBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithFetchMergeConfigBuilder extends + ConfigBuilder> +{ + private final Function mapper; + private String contentType; + private String initial; + private String path; + + + HttpKafkaWithFetchMergeConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithFetchMergeConfigBuilder contentType( + String contentType) + { + this.contentType = contentType; + return this; + } + + public HttpKafkaWithFetchMergeConfigBuilder initial( + String initial) + { + this.initial = initial; + return this; + } + + public HttpKafkaWithFetchMergeConfigBuilder path( + String path) + { + this.path = path; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithFetchMergeConfig(contentType, initial, path)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfig.java new file mode 100644 index 0000000000..788a487b93 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +public final class HttpKafkaWithProduceAsyncHeaderConfig +{ + public final String name; + public final String value; + + public HttpKafkaWithProduceAsyncHeaderConfig( + String name, + String value) + { + this.name = name; + this.value = value; + } + + public static HttpKafkaWithProduceAsyncHeaderConfigBuilder builder() + { + return new HttpKafkaWithProduceAsyncHeaderConfigBuilder<>(HttpKafkaWithProduceAsyncHeaderConfig.class::cast); + } + + public static HttpKafkaWithProduceAsyncHeaderConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithProduceAsyncHeaderConfigBuilder<>(mapper); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfigBuilder.java new file mode 100644 index 0000000000..2d595adf47 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfigBuilder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithProduceAsyncHeaderConfigBuilder extends + ConfigBuilder> +{ + private final Function mapper; + private String name; + private String value; + + + HttpKafkaWithProduceAsyncHeaderConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithProduceAsyncHeaderConfigBuilder name( + String name) + { + this.name = name; + return this; + } + + public HttpKafkaWithProduceAsyncHeaderConfigBuilder value( + String value) + { + this.value = value; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithProduceAsyncHeaderConfig(name, value)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfig.java similarity index 85% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfig.java rename to runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfig.java index 0bf34d99b3..746c60a7be 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfig.java @@ -12,12 +12,13 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.http.kafka.config; import static java.util.stream.Collectors.toList; import java.util.List; import java.util.Optional; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,4 +83,15 @@ private static Matcher asMatcher( .compile(wildcard.replaceAll("\\$\\{(?:params\\.)?([a-zA-Z_]+)\\}", "(?<$1>.+)")) .matcher(""); } + + public static HttpKafkaWithProduceConfigBuilder builder() + { + return new HttpKafkaWithProduceConfigBuilder<>(HttpKafkaWithProduceConfig.class::cast); + } + + public static HttpKafkaWithProduceConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithProduceConfigBuilder<>(mapper); + } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfigBuilder.java new file mode 100644 index 0000000000..c494ac0980 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfigBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.kafka.internal.types.KafkaAckMode; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithProduceConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String topic; + private KafkaAckMode acks; + private String key; + private List overrides; + private String replyTo; + private List async; + + + HttpKafkaWithProduceConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithProduceConfigBuilder topic( + String topic) + { + this.topic = topic; + return this; + } + + public HttpKafkaWithProduceConfigBuilder acks( + String acks) + { + this.acks = KafkaAckMode.valueOf(acks.toUpperCase()); + return this; + } + + public HttpKafkaWithProduceConfigBuilder key( + String key) + { + this.key = key; + return this; + } + + public HttpKafkaWithProduceConfigBuilder overrides( + List overrides) + { + this.overrides = overrides; + return this; + } + + public HttpKafkaWithProduceConfigBuilder replyTo( + String replyTo) + { + this.replyTo = replyTo; + return this; + } + + public HttpKafkaWithProduceConfigBuilder async( + List async) + { + this.async = async; + return this; + } + + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithProduceConfig(topic, acks, key, overrides, replyTo, async)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfig.java new file mode 100644 index 0000000000..4fe3ffb770 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +public final class HttpKafkaWithProduceOverrideConfig +{ + public final String name; + public final String value; + + public HttpKafkaWithProduceOverrideConfig( + String name, + String value) + { + this.name = name; + this.value = value; + } + + public static HttpKafkaWithProduceOverrideConfigBuilder builder() + { + return new HttpKafkaWithProduceOverrideConfigBuilder<>(HttpKafkaWithProduceOverrideConfig.class::cast); + } + + public static HttpKafkaWithProduceOverrideConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithProduceOverrideConfigBuilder<>(mapper); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfigBuilder.java new file mode 100644 index 0000000000..aa0f33b61d --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfigBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithProduceOverrideConfigBuilder extends + ConfigBuilder> +{ + private final Function mapper; + private String name; + private String value; + + + HttpKafkaWithProduceOverrideConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithProduceOverrideConfigBuilder name( + String name) + { + this.name = name; + return this; + } + + public HttpKafkaWithProduceOverrideConfigBuilder value( + String value) + { + this.value = value; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithProduceOverrideConfig(name, value)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapter.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapter.java index c7c4dfff7d..59d959afae 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapter.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapter.java @@ -68,6 +68,9 @@ public ConditionConfig adaptFromJson( ? object.getString(PATH_NAME) : null; - return new HttpKafkaConditionConfig(method, path); + return HttpKafkaConditionConfig.builder() + .method(method) + .path(path) + .build(); } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaRouteConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaRouteConfig.java index 0fe8a77de5..e3874b1768 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaRouteConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaRouteConfig.java @@ -26,6 +26,7 @@ import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaConditionConfig; import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; import io.aklivity.zilla.runtime.engine.config.RouteConfig; import io.aklivity.zilla.runtime.engine.util.function.LongObjectBiFunction; diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapter.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapter.java index f87d5841f9..23bb755f16 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapter.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapter.java @@ -17,6 +17,7 @@ import jakarta.json.JsonObject; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; import io.aklivity.zilla.runtime.binding.http.kafka.internal.HttpKafkaBinding; import io.aklivity.zilla.runtime.engine.config.WithConfig; import io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi; diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfigAdapter.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfigAdapter.java index 7a7f324f0c..e8edee9089 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfigAdapter.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfigAdapter.java @@ -24,6 +24,10 @@ import jakarta.json.JsonObjectBuilder; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchMergeConfig; + public final class HttpKafkaWithFetchConfigAdapter implements JsonbAdapter { private static final String CAPABILITY_NAME = "capability"; @@ -166,10 +170,20 @@ public HttpKafkaWithConfig adaptFromJson( path = patch.getString(MERGE_PATCH_PATH_NAME); } - newMerged = new HttpKafkaWithFetchMergeConfig(contentType, initial, path); + newMerged = HttpKafkaWithFetchMergeConfig.builder() + .contentType(contentType) + .initial(initial) + .path(path) + .build(); } } - return new HttpKafkaWithConfig(new HttpKafkaWithFetchConfig(newTopic, newFilters, newMerged)); + return HttpKafkaWithConfig.builder() + .fetch(HttpKafkaWithFetchConfig.builder() + .topic(newTopic) + .filters(newFilters) + .merged(newMerged) + .build()) + .build(); } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfigAdapter.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfigAdapter.java index 40285bef8c..51615ea3e8 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfigAdapter.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfigAdapter.java @@ -22,6 +22,10 @@ import jakarta.json.JsonObjectBuilder; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceAsyncHeaderConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceOverrideConfig; import io.aklivity.zilla.runtime.binding.http.kafka.internal.types.KafkaAckMode; public final class HttpKafkaWithProduceConfigAdapter implements JsonbAdapter @@ -114,7 +118,10 @@ public HttpKafkaWithConfig adaptFromJson( { String value = overrides.getString(name); - newOverrides.add(new HttpKafkaWithProduceOverrideConfig(name, value)); + newOverrides.add(HttpKafkaWithProduceOverrideConfig.builder() + .name(name) + .value(value) + .build()); } } @@ -132,11 +139,22 @@ public HttpKafkaWithConfig adaptFromJson( { String value = async.getString(name); - newAsync.add(new HttpKafkaWithProduceAsyncHeaderConfig(name, value)); + newAsync.add(HttpKafkaWithProduceAsyncHeaderConfig.builder() + .name(name) + .value(value) + .build()); } } - return new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig(newTopic, newAcks, newKey, newOverrides, newReplyTo, newAsync)); + return HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic(newTopic) + .acks(newAcks.name()) + .key(newKey) + .overrides(newOverrides) + .replyTo(newReplyTo) + .async(newAsync) + .build()) + .build(); } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithResolver.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithResolver.java index 913828184d..498abb03d4 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithResolver.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithResolver.java @@ -29,6 +29,12 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchMergeConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceAsyncHeaderConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceOverrideConfig; import io.aklivity.zilla.runtime.binding.http.kafka.internal.stream.HttpKafkaEtagHelper; import io.aklivity.zilla.runtime.binding.http.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.http.kafka.internal.types.HttpHeaderFW; diff --git a/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapterTest.java b/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapterTest.java index 3b10e971e2..2102a46622 100644 --- a/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapterTest.java +++ b/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapterTest.java @@ -59,7 +59,10 @@ public void shouldReadCondition() @Test public void shouldWriteCondition() { - HttpKafkaConditionConfig condition = new HttpKafkaConditionConfig("GET", "/test"); + HttpKafkaConditionConfig condition = HttpKafkaConditionConfig.builder() + .method("GET") + .path("/test") + .build(); String text = jsonb.toJson(condition); diff --git a/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapterTest.java b/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapterTest.java index 247ed4a33a..4ec1da57ac 100644 --- a/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapterTest.java +++ b/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapterTest.java @@ -18,7 +18,6 @@ import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresent; import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAnd; import static com.vtence.hamcrest.jpa.HasFieldWithValue.hasField; -import static io.aklivity.zilla.runtime.binding.http.kafka.internal.types.KafkaAckMode.IN_SYNC_REPLICAS; import static io.aklivity.zilla.runtime.binding.http.kafka.internal.types.KafkaAckMode.LEADER_ONLY; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; @@ -36,6 +35,13 @@ import org.junit.Before; import org.junit.Test; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchMergeConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceAsyncHeaderConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceOverrideConfig; + public class HttpKafkaWithConfigAdapterTest { private Jsonb jsonb; @@ -70,8 +76,13 @@ public void shouldReadWithFetchTopic() @Test public void shouldWriteWithFetchTopic() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithFetchConfig("test", null, null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .fetch(HttpKafkaWithFetchConfig.builder() + .topic("test") + .filters(null) + .merged(null) + .build()) + .build(); String text = jsonb.toJson(with); @@ -115,15 +126,17 @@ public void shouldReadWithFetchTopicAndFilters() @Test public void shouldWriteWithFetchTopicAndFilters() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithFetchConfig( - "test", - singletonList(new HttpKafkaWithFetchFilterConfig( + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .fetch(HttpKafkaWithFetchConfig.builder() + .topic("test") + .filters(singletonList(new HttpKafkaWithFetchFilterConfig( "fixed-key", singletonList(new HttpKafkaWithFetchFilterHeaderConfig( "tag", - "fixed-tag")))), - null)); + "fixed-tag"))))) + .merged(null) + .build()) + .build(); String text = jsonb.toJson(with); @@ -169,11 +182,17 @@ public void shouldReadWithFetchTopicAndMerge() @Test public void shouldWriteWithFetchTopicAndMerge() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithFetchConfig( - "test", - null, - new HttpKafkaWithFetchMergeConfig("application/json", "{\"data\":[]}", "/data/-"))); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .fetch(HttpKafkaWithFetchConfig.builder() + .topic("test") + .filters(null) + .merged(HttpKafkaWithFetchMergeConfig.builder() + .contentType("application/json") + .initial("{\"data\":[]}") + .path("/data/-") + .build()) + .build()) + .build(); String text = jsonb.toJson(with); @@ -210,8 +229,12 @@ public void shouldReadWithProduceTopic() @Test public void shouldWriteWithProduceTopic() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig("test", IN_SYNC_REPLICAS, null, null, null, null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .build()) + .build(); String text = jsonb.toJson(with); @@ -246,8 +269,12 @@ public void shouldReadWithProduceTopicAndAcks() @Test public void shouldWriteWithProduceTopicAndAcks() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig("test", LEADER_ONLY, null, null, null, null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("leader_only") + .build()) + .build(); String text = jsonb.toJson(with); @@ -281,8 +308,13 @@ public void shouldReadWithProduceTopicAndKey() @Test public void shouldWriteWithProduceTopicAndKey() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig("test", IN_SYNC_REPLICAS, "${params.id}", null, null, null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .key("${params.id}") + .build()) + .build(); String text = jsonb.toJson(with); @@ -323,14 +355,16 @@ public void shouldReadWithProduceTopicAndOverrides() @Test public void shouldWriteWithProduceTopicAndOverrides() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig( - "test", - IN_SYNC_REPLICAS, - null, - singletonList(new HttpKafkaWithProduceOverrideConfig("id", "${params.id}")), - null, - null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .overrides(singletonList(HttpKafkaWithProduceOverrideConfig.builder() + .name("id") + .value("${params.id}") + .build())) + .build()) + .build(); String text = jsonb.toJson(with); @@ -364,8 +398,13 @@ public void shouldReadWithProduceTopicAndReplyTo() @Test public void shouldWriteWithProduceTopicAndReplyTo() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig("test", IN_SYNC_REPLICAS, null, null, "replies", null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .replyTo("replies") + .build()) + .build(); String text = jsonb.toJson(with); @@ -406,15 +445,17 @@ public void shouldReadWithProduceTopicAndAsync() @Test public void shouldWriteWithProduceTopicAndAsync() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig( - "test", - IN_SYNC_REPLICAS, - null, - null, - null, - singletonList( - new HttpKafkaWithProduceAsyncHeaderConfig("location", "/items/${params.id};${correlationId}")))); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .async(singletonList( + HttpKafkaWithProduceAsyncHeaderConfig.builder() + .name("location") + .value("/items/${params.id};${correlationId}") + .build())) + .build()) + .build(); String text = jsonb.toJson(with); From 204deb0601e4636841dba640ea2dcb9e2a222202 Mon Sep 17 00:00:00 2001 From: bmaidics Date: Fri, 1 Mar 2024 17:23:37 +0100 Subject: [PATCH 22/25] Catalog (#825) --- incubator/binding-asyncapi/pom.xml | 5 -- ...AsyncapiClientCompositeBindingAdapter.java | 1 + .../AsyncapiCompositeBindingAdapter.java | 86 +++++++++++++++++++ .../asyncapi/internal/AsyncapiProtocol.java | 6 +- ...AsyncapiServerCompositeBindingAdapter.java | 5 +- .../src/main/moditect/module-info.java | 3 +- .../internal/config/MqttBindingConfig.java | 38 ++++++-- 7 files changed, 127 insertions(+), 17 deletions(-) diff --git a/incubator/binding-asyncapi/pom.xml b/incubator/binding-asyncapi/pom.xml index 5ea51f49a4..cd85fcc214 100644 --- a/incubator/binding-asyncapi/pom.xml +++ b/incubator/binding-asyncapi/pom.xml @@ -108,11 +108,6 @@ jackson-dataformat-yaml 2.16.1 - - com.fasterxml.jackson.core - jackson-databind - 2.16.1 - ${project.groupId} engine diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java index 5926ffbab6..2beccbbd22 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java @@ -49,6 +49,7 @@ public BindingConfig adapt( return BindingConfig.builder(binding) .composite() .name(String.format("%s.%s", qname, "$composite")) + .inject(n -> this.injectCatalog(n, asyncapi)) .inject(protocol::injectProtocolClientCache) .binding() .name(String.format("%s_client0", protocol.scheme)) diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java index 814564ae57..127a2afbba 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java @@ -14,15 +14,35 @@ */ package io.aklivity.zilla.runtime.binding.asyncapi.internal; +import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.MINIMIZE_QUOTES; +import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.WRITE_DOC_START_MARKER; +import static org.agrona.LangUtil.rethrowUnchecked; + import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; + import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSchema; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiSchemaView; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineOptionsConfig; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineSchemaConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; public class AsyncapiCompositeBindingAdapter { + protected static final String INLINE_CATALOG_NAME = "catalog0"; + protected static final String INLINE_CATALOG_TYPE = "inline"; + protected static final String VERSION_LATEST = "latest"; protected static final String APPLICATION_JSON = "application/json"; protected Asyncapi asyncapi; @@ -62,4 +82,70 @@ protected AsyncapiProtocol resolveProtocol( } return protocol; } + + protected NamespaceConfigBuilder injectCatalog( + NamespaceConfigBuilder namespace, + Asyncapi asyncapi) + { + if (asyncapi.components != null && asyncapi.components.schemas != null && !asyncapi.components.schemas.isEmpty()) + { + namespace + .catalog() + .name(INLINE_CATALOG_NAME) + .type(INLINE_CATALOG_TYPE) + .options(InlineOptionsConfig::builder) + .subjects() + .inject(this::injectSubjects) + .build() + .build() + .build(); + + } + return namespace; + } + + protected InlineSchemaConfigBuilder injectSubjects( + InlineSchemaConfigBuilder subjects) + { + try (Jsonb jsonb = JsonbBuilder.create()) + { + YAMLMapper yaml = YAMLMapper.builder() + .disable(WRITE_DOC_START_MARKER) + .enable(MINIMIZE_QUOTES) + .build(); + for (Map.Entry entry : asyncapi.components.schemas.entrySet()) + { + AsyncapiSchemaView schema = AsyncapiSchemaView.of(asyncapi.components.schemas, entry.getValue()); + subjects + .subject(entry.getKey()) + .version(VERSION_LATEST) + .schema(writeSchemaYaml(jsonb, yaml, schema)) + .build(); + } + } + catch (Exception ex) + { + rethrowUnchecked(ex); + } + return subjects; + } + + protected static String writeSchemaYaml( + Jsonb jsonb, + YAMLMapper yaml, + Object schema) + { + String result = null; + try + { + String schemaJson = jsonb.toJson(schema); + JsonNode json = new ObjectMapper().readTree(schemaJson); + result = yaml.writeValueAsString(json); + } + catch (JsonProcessingException ex) + { + rethrowUnchecked(ex); + } + return result; + } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java index ea896536cb..a3774df8cc 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java @@ -33,6 +33,7 @@ public abstract class AsyncapiProtocol { protected static final String INLINE_CATALOG_NAME = "catalog0"; protected static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + private static final String VERSION_LATEST = "latest"; protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); @@ -85,8 +86,9 @@ protected CatalogedConfigBuilder injectJsonSchemas( { cataloged .schema() - .subject(schema) - .build() + .version(VERSION_LATEST) + .subject(schema) + .build() .build(); } else diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java index 7559286e09..56712d6fdd 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java @@ -45,7 +45,7 @@ public BindingConfig adapt( AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; AsyncapiConfig asyncapiConfig = options.specs.get(0); this.asyncapi = asyncapiConfig.asyncapi; - AsyncapiView asyncapiView = AsyncapiView.of(asyncapi); + AsyncapiView view = AsyncapiView.of(asyncapi); //TODO: add composite for all servers AsyncapiServerView firstServer = AsyncapiServerView.of(asyncapi.servers.entrySet().iterator().next().getValue()); @@ -53,13 +53,14 @@ public BindingConfig adapt( this.qname = binding.qname; this.qvault = binding.qvault; this.protocol = resolveProtocol(firstServer.protocol(), options); - int[] allPorts = asyncapiView.resolveAllPorts(); + int[] allPorts = view.resolveAllPorts(); this.compositePorts = protocol.resolvePorts(); this.isTlsEnabled = protocol.isSecure(); return BindingConfig.builder(binding) .composite() .name(String.format("%s/%s", qname, protocol.scheme)) + .inject(n -> this.injectCatalog(n, asyncapi)) .binding() .name("tcp_server0") .type("tcp") diff --git a/incubator/binding-asyncapi/src/main/moditect/module-info.java b/incubator/binding-asyncapi/src/main/moditect/module-info.java index fe146ddc7b..54b0d936b6 100644 --- a/incubator/binding-asyncapi/src/main/moditect/module-info.java +++ b/incubator/binding-asyncapi/src/main/moditect/module-info.java @@ -14,8 +14,8 @@ */ module io.aklivity.zilla.runtime.binding.asyncapi { - requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.dataformat.yaml; requires io.aklivity.zilla.runtime.engine; requires io.aklivity.zilla.runtime.binding.mqtt; requires io.aklivity.zilla.runtime.binding.http; @@ -49,4 +49,5 @@ provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi with io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingAdapter; + } diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/config/MqttBindingConfig.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/config/MqttBindingConfig.java index 875be68ee9..3242ec645a 100644 --- a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/config/MqttBindingConfig.java +++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/config/MqttBindingConfig.java @@ -18,13 +18,13 @@ import static java.util.stream.Collectors.toList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.function.ToLongFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import io.aklivity.zilla.runtime.binding.mqtt.config.MqttCredentialsConfig; import io.aklivity.zilla.runtime.binding.mqtt.config.MqttOptionsConfig; @@ -47,7 +47,7 @@ public final class MqttBindingConfig public final MqttOptionsConfig options; public final List routes; public final Function credentials; - public final Map topics; + public final Map topics; public final List versions; public final ToLongFunction resolveId; public final GuardHandler guard; @@ -64,10 +64,20 @@ public MqttBindingConfig( this.resolveId = binding.resolveId; this.credentials = options != null && options.authorization != null ? asAccessor(options.authorization.credentials) : DEFAULT_CREDENTIALS; - this.topics = options != null && - options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap(t -> t.name, t -> t.content)) : null; + this.topics = new HashMap<>(); + if (options != null && options.topics != null) + { + options.topics.forEach(t -> + { + String pattern = t.name.replace(".", "\\.") + .replace("$", "\\$") + .replace("+", "[^/]*") + .replace("#", ".*"); + topics.put(Pattern.compile(pattern).matcher(""), t.content); + }); + } + + this.guard = resolveGuard(context); this.versions = options != null && options.versions != null ? options.versions : DEFAULT_VERSIONS; @@ -115,7 +125,21 @@ public MqttRouteConfig resolvePublish( public ModelConfig supplyModelConfig( String topic) { - return topics != null ? topics.getOrDefault(topic, null) : null; + ModelConfig config = null; + if (topics != null) + { + for (Map.Entry t : topics.entrySet()) + { + final Matcher matcher = t.getKey(); + matcher.reset(topic); + if (matcher.find()) + { + config = t.getValue(); + break; + } + } + } + return config; } public Function credentials() From 08deac544160d202e80a88a682ab2c53dca831b9 Mon Sep 17 00:00:00 2001 From: Akram Yakubov Date: Mon, 4 Mar 2024 07:55:02 -0800 Subject: [PATCH 23/25] Asyncapi and Openapi bug fixes (#826) --- .../asyncapi/internal/AsyncapiProtocol.java | 2 +- .../internal/AyncapiKafkaProtocol.java | 5 +- .../internal/model/AsyncapiMessage.java | 1 + .../internal/view/AsyncapiMessageView.java | 5 ++ .../internal/view/AsyncapiSchemaView.java | 5 ++ .../OpenapiAsyncCompositeBindingAdapter.java | 71 +++++++++++++++++-- .../OpenapiClientCompositeBindingAdapter.java | 38 +++++----- .../OpenapiServerCompositeBindingAdapter.java | 71 +++++++++++-------- .../openapi/internal/model/Openapi.java | 4 +- ...BearerAuth.java => OpenapiBearerAuth.java} | 2 +- ...Components.java => OpenapiComponents.java} | 6 +- ...{OpenApiHeader.java => OpenapiHeader.java} | 4 +- .../{OpenApiItem.java => OpenapiItem.java} | 2 +- ...piMediaType.java => OpenapiMediaType.java} | 4 +- ...piOperation.java => OpenapiOperation.java} | 6 +- ...piParameter.java => OpenapiParameter.java} | 4 +- .../internal/model/OpenapiPathItem.java | 16 ++--- ...questBody.java => OpenapiRequestBody.java} | 4 +- ...nApiResponse.java => OpenapiResponse.java} | 4 +- .../model/OpenapiResponseByContentType.java | 4 +- ...{OpenApiSchema.java => OpenapiSchema.java} | 6 +- ...Scheme.java => OpenapiSecurityScheme.java} | 2 +- ...{OpenApiServer.java => OpenapiServer.java} | 2 +- ...ionView.java => OpenapiOperationView.java} | 16 ++--- ...nsView.java => OpenapiOperationsView.java} | 18 ++--- ...nApiPathView.java => OpenapiPathView.java} | 20 +++--- ...Resolvable.java => OpenapiResolvable.java} | 4 +- ...SchemaView.java => OpenapiSchemaView.java} | 39 ++++++---- ...ServerView.java => OpenapiServerView.java} | 14 ++-- .../inline/internal/InlineCatalogHandler.java | 4 +- .../model/json/internal/JsonModelHandler.java | 5 +- .../internal/JsonReadConverterHandler.java | 4 +- .../json/internal/JsonValidatorHandler.java | 1 - .../internal/stream/HttpServerFactory.java | 2 +- 34 files changed, 244 insertions(+), 151 deletions(-) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiBearerAuth.java => OpenapiBearerAuth.java} (95%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiComponents.java => OpenapiComponents.java} (82%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiHeader.java => OpenapiHeader.java} (91%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiItem.java => OpenapiItem.java} (96%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiMediaType.java => OpenapiMediaType.java} (91%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiOperation.java => OpenapiOperation.java} (87%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiParameter.java => OpenapiParameter.java} (92%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiRequestBody.java => OpenapiRequestBody.java} (89%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiResponse.java => OpenapiResponse.java} (91%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiSchema.java => OpenapiSchema.java} (89%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiSecurityScheme.java => OpenapiSecurityScheme.java} (95%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/{OpenApiServer.java => OpenapiServer.java} (96%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/{OpenApiOperationView.java => OpenapiOperationView.java} (86%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/{OpenApiOperationsView.java => OpenapiOperationsView.java} (81%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/{OpenApiPathView.java => OpenapiPathView.java} (80%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/{OpenApiResolvable.java => OpenapiResolvable.java} (94%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/{OpenApiSchemaView.java => OpenapiSchemaView.java} (65%) rename incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/{OpenApiServerView.java => OpenapiServerView.java} (79%) diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java index a3774df8cc..66ab35de82 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java @@ -33,7 +33,7 @@ public abstract class AsyncapiProtocol { protected static final String INLINE_CATALOG_NAME = "catalog0"; protected static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); - private static final String VERSION_LATEST = "latest"; + protected static final String VERSION_LATEST = "latest"; protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java index 1e2e27860e..ec7e1243b2 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java @@ -24,6 +24,7 @@ import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiSchemaView; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfigBuilder; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; @@ -194,10 +195,12 @@ private CatalogedConfigBuilder injectSchemas( for (String name : messages.keySet()) { AsyncapiMessageView message = AsyncapiMessageView.of(asyncApi.components.messages, messages.get(name)); - String subject = message.refKey() != null ? message.refKey() : name; + AsyncapiSchemaView payload = AsyncapiSchemaView.of(asyncApi.components.schemas, message.payload()); + String subject = payload.refKey() != null ? payload.refKey() : name; catalog .schema() .subject(subject) + .version(VERSION_LATEST) .build() .build(); } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java index b0f3b8f93c..70fd6ab122 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java @@ -20,6 +20,7 @@ public class AsyncapiMessage { public AsyncapiSchema headers; public String contentType; + public AsyncapiSchema payload; @JsonbProperty("$ref") public String ref; diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java index e5535ea784..a1087313ad 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java @@ -38,6 +38,11 @@ public String contentType() return message.contentType; } + public AsyncapiSchema payload() + { + return message.payload; + } + public static AsyncapiMessageView of( Map messages, AsyncapiMessage asyncapiMessage) diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java index d92de500f8..8755f88bb2 100644 --- a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java @@ -40,6 +40,11 @@ public String getType() return schema.type; } + public String refKey() + { + return key; + } + public AsyncapiSchemaView getItems() { return schema.items == null ? null : AsyncapiSchemaView.of(schemas, schema.items); diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java index 3badc8d43c..81344c31f0 100644 --- a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java @@ -18,6 +18,9 @@ import static java.util.stream.Collectors.toList; import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; @@ -26,12 +29,18 @@ import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfigBuilder; import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchMergeConfig; import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiSpecConfig; import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponse; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponseByContentType; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiSchemaView; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; @@ -39,6 +48,10 @@ public final class OpenapiAsyncCompositeBindingAdapter implements CompositeBindingAdapterSpi { + private static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + + private final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); + @Override public String type() { @@ -89,20 +102,22 @@ private BindingConfigBuilder injectHttpKafkaRoutes( for (String item : openapi.paths.keySet()) { - OpenApiPathView path = OpenApiPathView.of(openapi.paths.get(item)); + OpenapiPathView path = OpenapiPathView.of(openapi.paths.get(item)); for (String method : path.methods().keySet()) { final String operationId = condition.operationId != null ? condition.operationId : path.methods().get(method).operationId; - final AsyncapiOperation operation = asyncapi.operations.entrySet().stream() + final OpenapiOperation openapiOperation = path.methods().get(method); + + final AsyncapiOperation asyncapiOperation = asyncapi.operations.entrySet().stream() .filter(f -> f.getKey().equals(operationId)) .map(v -> v.getValue()) .findFirst() .get(); final AsyncapiChannelView channel = AsyncapiChannelView - .of(asyncapi.channels, operation.channel); + .of(asyncapi.channels, asyncapiOperation.channel); binding .route() @@ -111,7 +126,8 @@ private BindingConfigBuilder injectHttpKafkaRoutes( .method(method) .path(item) .build() - .inject(r -> injectHttpKafkaRouteWith(r, operation.action, channel.address())) + .inject(r -> injectHttpKafkaRouteWith(r, openapi, openapiOperation, + asyncapiOperation.action, channel.address())) .build(); } } @@ -123,6 +139,8 @@ private BindingConfigBuilder injectHttpKafkaRoutes( private RouteConfigBuilder injectHttpKafkaRouteWith( RouteConfigBuilder route, + Openapi openapi, + OpenapiOperation operation, String action, String address) { @@ -133,6 +151,7 @@ private RouteConfigBuilder injectHttpKafkaRouteWith( case "receive": newWith.fetch(HttpKafkaWithFetchConfig.builder() .topic(address) + .inject(with -> this.injectHttpKafkaRouteFetchWith(with, openapi, operation)) .build()); break; case "send": @@ -147,4 +166,46 @@ private RouteConfigBuilder injectHttpKafkaRouteWith( return route; } + + private HttpKafkaWithFetchConfigBuilder injectHttpKafkaRouteFetchWith( + HttpKafkaWithFetchConfigBuilder fetch, + Openapi openapi, + OpenapiOperation operation) + { + merge: + for (Map.Entry response : operation.responses.entrySet()) + { + OpenapiSchemaView schema = resolveSchemaForJsonContentType(response.getValue().content, openapi); + + if ("array".equals(schema.getType())) + { + fetch.merged(HttpKafkaWithFetchMergeConfig.builder() + .contentType("application/json") + .build()); + break merge; + } + + } + return fetch; + } + + private OpenapiSchemaView resolveSchemaForJsonContentType( + Map content, + Openapi openApi) + { + OpenapiResponse response = null; + if (content != null) + { + for (String contentType : content.keySet()) + { + if (jsonContentType.reset(contentType).matches()) + { + response = content.get(contentType); + break; + } + } + } + + return response == null ? null : OpenapiSchemaView.of(openApi.components.schemas, response.schema); + } } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java index 2318346547..c49479ef35 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java @@ -28,16 +28,16 @@ import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiHeader; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiResponse; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiHeader; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponse; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponseByContentType; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiOperationView; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiOperationsView; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiSchemaView; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiServerView; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiOperationView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiOperationsView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiSchemaView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiServerView; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; @@ -119,9 +119,9 @@ private URI findFirstServerUrlWithScheme( { requireNonNull(scheme); URI result = null; - for (OpenApiServer item : openApi.servers) + for (OpenapiServer item : openApi.servers) { - OpenApiServerView server = OpenApiServerView.of(item); + OpenapiServerView server = OpenapiServerView.of(item); if (scheme.equals(server.url().getScheme())) { result = server.url(); @@ -135,7 +135,7 @@ private BindingConfigBuilder injectHttpClientOptions( BindingConfigBuilder binding, Openapi openApi) { - OpenApiOperationsView operations = OpenApiOperationsView.of(openApi.paths); + OpenapiOperationsView operations = OpenapiOperationsView.of(openApi.paths); if (operations.hasResponses()) { binding. @@ -147,16 +147,16 @@ private BindingConfigBuilder injectHttpClientOptions( } private HttpOptionsConfigBuilder injectHttpClientRequests( - OpenApiOperationsView operations, + OpenapiOperationsView operations, HttpOptionsConfigBuilder options, Openapi openApi) { for (String pathName : openApi.paths.keySet()) { - OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(pathName)); + OpenapiPathView path = OpenapiPathView.of(openApi.paths.get(pathName)); for (String methodName : path.methods().keySet()) { - OpenApiOperationView operation = operations.operation(pathName, methodName); + OpenapiOperationView operation = operations.operation(pathName, methodName); if (operation.hasResponses()) { options @@ -174,7 +174,7 @@ private HttpOptionsConfigBuilder injectHttpClientRequests( private HttpRequestConfigBuilder injectResponses( HttpRequestConfigBuilder request, - OpenApiOperationView operation, + OpenapiOperationView operation, Openapi openApi) { if (operation != null && operation.responsesByStatus() != null) @@ -183,11 +183,11 @@ private HttpRequestConfigBuilder injectResponses( { String status = responses0.getKey(); OpenapiResponseByContentType responses1 = responses0.getValue(); - if (!(OpenApiOperationView.DEFAULT.equals(status)) && responses1.content != null) + if (!(OpenapiOperationView.DEFAULT.equals(status)) && responses1.content != null) { - for (Map.Entry response2 : responses1.content.entrySet()) + for (Map.Entry response2 : responses1.content.entrySet()) { - OpenApiSchemaView schema = OpenApiSchemaView.of(openApi.components.schemas, response2.getValue().schema); + OpenapiSchemaView schema = OpenapiSchemaView.of(openApi.components.schemas, response2.getValue().schema); request .response() .status(Integer.parseInt(status)) @@ -215,7 +215,7 @@ private HttpResponseConfigBuilder injectResponseHeaders( { if (responses.headers != null && !responses.headers.isEmpty()) { - for (Map.Entry header : responses.headers.entrySet()) + for (Map.Entry header : responses.headers.entrySet()) { String name = header.getKey(); ModelConfig model = models.get(header.getValue().schema.type); diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java index efa232860b..b98aca9058 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java @@ -39,15 +39,15 @@ import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiMediaType; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiParameter; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiSchema; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiPathView; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiSchemaView; -import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenApiServerView; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiMediaType; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiParameter; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiSchema; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiSchemaView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiServerView; import io.aklivity.zilla.runtime.binding.tcp.config.TcpConditionConfig; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; @@ -206,20 +206,23 @@ private HttpOptionsConfigBuilder injectHttpServerOptions( private HttpOptionsConfigBuilder injectHttpServerRequests( HttpOptionsConfigBuilder options, - Openapi openApi) + Openapi openapi) { - for (String pathName : openApi.paths.keySet()) + for (String pathName : openapi.paths.keySet()) { - OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(pathName)); + OpenapiPathView path = OpenapiPathView.of(openapi.paths.get(pathName)); for (String methodName : path.methods().keySet()) { - OpenApiOperation operation = path.methods().get(methodName); - if (operation.requestBody != null || operation.parameters != null && !operation.parameters.isEmpty()) + 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(); } @@ -230,12 +233,14 @@ private HttpOptionsConfigBuilder injectHttpServerRequests( private HttpRequestConfigBuilder injectContent( HttpRequestConfigBuilder request, - OpenApiOperation operation, + OpenapiOperation operation, Openapi openApi) { - if (operation.requestBody != null && operation.requestBody.content != null && !operation.requestBody.content.isEmpty()) + if (operation.requestBody != null && + operation.requestBody.content != null && + !operation.requestBody.content.isEmpty()) { - OpenApiSchemaView schema = resolveSchemaForJsonContentType(operation.requestBody.content, openApi); + OpenapiSchemaView schema = resolveSchemaForJsonContentType(operation.requestBody.content, openApi); if (schema != null) { request. @@ -244,6 +249,7 @@ private HttpRequestConfigBuilder injectContent( .name(INLINE_CATALOG_NAME) .schema() .subject(schema.refKey()) + .version(VERSION_LATEST) .build() .build() .build(); @@ -254,11 +260,11 @@ private HttpRequestConfigBuilder injectContent( private HttpRequestConfigBuilder injectParams( HttpRequestConfigBuilder request, - OpenApiOperation operation) + OpenapiOperation operation) { if (operation != null && operation.parameters != null) { - for (OpenApiParameter parameter : operation.parameters) + for (OpenapiParameter parameter : operation.parameters) { if (parameter.schema != null && parameter.schema.type != null) { @@ -305,7 +311,7 @@ private BindingConfigBuilder injectHttpServerRoutes( { for (String item : openApi.paths.keySet()) { - OpenApiPathView path = OpenApiPathView.of(openApi.paths.get(item)); + OpenapiPathView path = OpenapiPathView.of(openApi.paths.get(item)); for (String method : path.methods().keySet()) { binding @@ -324,7 +330,7 @@ private BindingConfigBuilder injectHttpServerRoutes( private RouteConfigBuilder injectHttpServerRouteGuarded( RouteConfigBuilder route, - OpenApiPathView path, + OpenapiPathView path, String method, String guardName, Map securitySchemes) @@ -391,13 +397,16 @@ private InlineSchemaConfigBuilder injectSubjects( { try (Jsonb jsonb = JsonbBuilder.create()) { - for (Map.Entry entry : openApi.components.schemas.entrySet()) + for (Map.Entry entry : openApi.components.schemas.entrySet()) { + OpenapiSchemaView schemaView = OpenapiSchemaView.of(openApi.components.schemas, entry.getValue()); + OpenapiSchema schema = schemaView.ref() != null ? schemaView.ref() : entry.getValue(); + subjects .subject(entry.getKey()) - .version(VERSION_LATEST) - .schema(jsonb.toJson(openApi.components.schemas)) - .build(); + .schema(jsonb.toJson(schema)) + .version(VERSION_LATEST) + .build(); } } catch (Exception ex) @@ -413,7 +422,7 @@ private int[] resolveAllPorts( int[] ports = new int[openApi.servers.size()]; for (int i = 0; i < openApi.servers.size(); i++) { - OpenApiServerView server = OpenApiServerView.of(openApi.servers.get(i)); + OpenapiServerView server = OpenapiServerView.of(openApi.servers.get(i)); URI url = server.url(); ports[i] = url.getPort(); } @@ -440,9 +449,9 @@ private URI findFirstServerUrlWithScheme( { requireNonNull(scheme); URI result = null; - for (OpenApiServer item : openApi.servers) + for (OpenapiServer item : openApi.servers) { - OpenApiServerView server = OpenApiServerView.of(item); + OpenapiServerView server = OpenapiServerView.of(item); if (scheme.equals(server.url().getScheme())) { result = server.url(); @@ -472,11 +481,11 @@ private Map resolveSecuritySchemes( return result; } - private OpenApiSchemaView resolveSchemaForJsonContentType( - Map content, + private OpenapiSchemaView resolveSchemaForJsonContentType( + Map content, Openapi openApi) { - OpenApiMediaType mediaType = null; + OpenapiMediaType mediaType = null; if (content != null) { for (String contentType : content.keySet()) @@ -489,6 +498,6 @@ private OpenApiSchemaView resolveSchemaForJsonContentType( } } - return mediaType == null ? null : OpenApiSchemaView.of(openApi.components.schemas, mediaType.schema); + return mediaType == null ? null : OpenapiSchemaView.of(openApi.components.schemas, mediaType.schema); } } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java index 45a5a566bf..f9a144fa31 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java @@ -20,7 +20,7 @@ public class Openapi { public String openapi; - public List servers; + public List servers; public Map paths; - public OpenApiComponents components; + public OpenapiComponents components; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiBearerAuth.java similarity index 95% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiBearerAuth.java index db34f20fff..0d781d1425 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiBearerAuth.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiBearerAuth.java @@ -14,7 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class OpenApiBearerAuth +public class OpenapiBearerAuth { public String bearerFormat; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiComponents.java similarity index 82% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiComponents.java index 1f31bb5592..b57f8016f4 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiComponents.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiComponents.java @@ -16,8 +16,8 @@ import java.util.Map; -public class OpenApiComponents +public class OpenapiComponents { - public Map securitySchemes; - public Map schemas; + public Map securitySchemes; + public Map schemas; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiHeader.java similarity index 91% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiHeader.java index f91603be21..634c68d604 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiHeader.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiHeader.java @@ -14,7 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class OpenApiHeader +public class OpenapiHeader { - public OpenApiSchema schema; + public OpenapiSchema schema; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiItem.java similarity index 96% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiItem.java index b7ff06d8e3..852493a3e3 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiItem.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiItem.java @@ -14,7 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class OpenApiItem +public class OpenapiItem { public String type; public String description; diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiMediaType.java similarity index 91% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiMediaType.java index 67635b7d26..fb74808582 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiMediaType.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiMediaType.java @@ -14,7 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class OpenApiMediaType +public class OpenapiMediaType { - public OpenApiSchema schema; + public OpenapiSchema schema; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiOperation.java similarity index 87% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiOperation.java index b6e69f0408..a400d5c838 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiOperation.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiOperation.java @@ -17,11 +17,11 @@ import java.util.List; import java.util.Map; -public class OpenApiOperation +public class OpenapiOperation { public List>> security; public String operationId; - public OpenApiRequestBody requestBody; - public List parameters; + public OpenapiRequestBody requestBody; + public List parameters; public Map responses; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiParameter.java similarity index 92% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiParameter.java index fb713f91d3..585ed26242 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiParameter.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiParameter.java @@ -14,10 +14,10 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class OpenApiParameter +public class OpenapiParameter { public String name; public String in; public boolean required; - public OpenApiSchema schema; + public OpenapiSchema schema; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java index 8eddf9c820..28e3910280 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java @@ -16,12 +16,12 @@ public class OpenapiPathItem { - public OpenApiOperation get; - public OpenApiOperation put; - public OpenApiOperation post; - public OpenApiOperation delete; - public OpenApiOperation options; - public OpenApiOperation head; - public OpenApiOperation patch; - public OpenApiOperation trace; + public OpenapiOperation get; + public OpenapiOperation put; + public OpenapiOperation post; + public OpenapiOperation delete; + public OpenapiOperation options; + public OpenapiOperation head; + public OpenapiOperation patch; + public OpenapiOperation trace; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiRequestBody.java similarity index 89% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiRequestBody.java index 245899b8ee..8e63e2eda7 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiRequestBody.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiRequestBody.java @@ -16,7 +16,7 @@ import java.util.Map; -public class OpenApiRequestBody +public class OpenapiRequestBody { - public Map content; + public Map content; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponse.java similarity index 91% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponse.java index 3f23331a88..35bb9bb49e 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiResponse.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponse.java @@ -14,7 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class OpenApiResponse +public class OpenapiResponse { - public OpenApiSchema schema; + public OpenapiSchema schema; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java index c79664532b..b75951a387 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java @@ -18,6 +18,6 @@ public class OpenapiResponseByContentType { - public Map headers; - public Map content; + public Map headers; + public Map content; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSchema.java similarity index 89% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSchema.java index 855472a883..c78a8b2a9f 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSchema.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSchema.java @@ -19,11 +19,11 @@ import jakarta.json.bind.annotation.JsonbProperty; -public class OpenApiSchema +public class OpenapiSchema { public String type; - public OpenApiSchema items; - public Map properties; + public OpenapiSchema items; + public Map properties; public List required; @JsonbProperty("$ref") diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSecurityScheme.java similarity index 95% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSecurityScheme.java index 4d27f750ea..d372dd908e 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiSecurityScheme.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSecurityScheme.java @@ -14,7 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class OpenApiSecurityScheme +public class OpenapiSecurityScheme { public String bearerFormat; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiServer.java similarity index 96% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiServer.java index da292428f3..b62d40eeec 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenApiServer.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiServer.java @@ -14,7 +14,7 @@ */ package io.aklivity.zilla.runtime.binding.openapi.internal.model; -public class OpenApiServer +public class OpenapiServer { public String url; } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationView.java similarity index 86% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationView.java index 45123ef155..e85a07d06c 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationView.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationView.java @@ -16,18 +16,18 @@ import java.util.Map; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiOperation; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponseByContentType; -public final class OpenApiOperationView +public final class OpenapiOperationView { public static final String DEFAULT = "default"; - private final OpenApiOperation operation; + private final OpenapiOperation operation; private final boolean hasResponses; - private OpenApiOperationView( - OpenApiOperation operation) + private OpenapiOperationView( + OpenapiOperation operation) { this.operation = operation; this.hasResponses = initHasResponses(); @@ -62,9 +62,9 @@ private boolean initHasResponses() return result; } - public static OpenApiOperationView of( - OpenApiOperation operation) + public static OpenapiOperationView of( + OpenapiOperation operation) { - return new OpenApiOperationView(operation); + return new OpenapiOperationView(operation); } } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationsView.java similarity index 81% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationsView.java index d2527e84d1..fe735b269d 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiOperationsView.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationsView.java @@ -21,9 +21,9 @@ import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; -public final class OpenApiOperationsView +public final class OpenapiOperationsView { - private final Map> operationsByPath; + private final Map> operationsByPath; private final boolean hasResponses; public boolean hasResponses() @@ -31,30 +31,30 @@ public boolean hasResponses() return this.hasResponses; } - public OpenApiOperationView operation( + public OpenapiOperationView operation( String pathName, String methodName) { return operationsByPath.get(pathName).get(methodName); } - public static OpenApiOperationsView of( + public static OpenapiOperationsView of( Map paths) { - return new OpenApiOperationsView(paths); + return new OpenapiOperationsView(paths); } - private OpenApiOperationsView( + private OpenapiOperationsView( Map paths) { this.operationsByPath = new Object2ObjectHashMap<>(); boolean hasResponses = false; for (String pathName : paths.keySet()) { - OpenApiPathView path = OpenApiPathView.of(paths.get(pathName)); + OpenapiPathView path = OpenapiPathView.of(paths.get(pathName)); for (String methodName : path.methods().keySet()) { - OpenApiOperationView operation = OpenApiOperationView.of(path.methods().get(methodName)); + OpenapiOperationView operation = OpenapiOperationView.of(path.methods().get(methodName)); hasResponses |= operation.hasResponses(); if (operationsByPath.containsKey(pathName)) { @@ -62,7 +62,7 @@ private OpenApiOperationsView( } else { - Map operationsPerMethod = new LinkedHashMap<>(); + Map operationsPerMethod = new LinkedHashMap<>(); operationsPerMethod.put(methodName, operation); operationsByPath.put(pathName, operationsPerMethod); } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiPathView.java similarity index 80% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiPathView.java index 3fbee83d26..1caac5e5b1 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiPathView.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiPathView.java @@ -19,28 +19,28 @@ import java.util.LinkedHashMap; import java.util.Map; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiOperation; import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; -public final class OpenApiPathView +public final class OpenapiPathView { - private final Map methods; + private final Map methods; - public Map methods() + public Map methods() { return methods; } - public static OpenApiPathView of( + public static OpenapiPathView of( OpenapiPathItem pathItem) { - return new OpenApiPathView(pathItem); + return new OpenapiPathView(pathItem); } - private OpenApiPathView( + private OpenapiPathView( OpenapiPathItem pathItem) { - Map methods = new LinkedHashMap<>(); + Map methods = new LinkedHashMap<>(); putIfNotNull(methods, "GET", pathItem.get); putIfNotNull(methods, "PUT", pathItem.put); putIfNotNull(methods, "POST", pathItem.post); @@ -53,9 +53,9 @@ private OpenApiPathView( } private static void putIfNotNull( - Map methods, + Map methods, String method, - OpenApiOperation operation) + OpenapiOperation operation) { if (operation != null) { diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiResolvable.java similarity index 94% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiResolvable.java index fc5babcde0..dd6c47b837 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiResolvable.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiResolvable.java @@ -18,14 +18,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public abstract class OpenApiResolvable +public abstract class OpenapiResolvable { private final Map map; private final Matcher matcher; protected String key; - public OpenApiResolvable( + public OpenapiResolvable( Map map, String regex) { diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiSchemaView.java similarity index 65% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiSchemaView.java index 29eddefb26..366bf5ad78 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiSchemaView.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiSchemaView.java @@ -19,8 +19,8 @@ import jakarta.json.bind.annotation.JsonbPropertyOrder; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiItem; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiSchema; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiSchema; @JsonbPropertyOrder({ "type", @@ -28,26 +28,31 @@ "properties", "required" }) -public final class OpenApiSchemaView extends OpenApiResolvable +public final class OpenapiSchemaView extends OpenapiResolvable { private static final String ARRAY_TYPE = "array"; - private final OpenApiSchema schema; - private final Map schemas; + private final OpenapiSchema schema; + private final Map schemas; + private final OpenapiSchema schemaRef; - private OpenApiSchemaView( - Map schemas, - OpenApiSchema schema) + private OpenapiSchemaView( + Map schemas, + OpenapiSchema schema) { super(schemas, "#/components/schemas/(\\w+)"); + OpenapiSchema schemaRef = null; if (schema.ref != null) { + schemaRef = new OpenapiSchema(); + schemaRef.ref = schema.ref; schema = resolveRef(schema.ref); } else if (ARRAY_TYPE.equals(schema.type) && schema.items != null && schema.items.ref != null) { schema.items = resolveRef(schema.items.ref); } + this.schemaRef = schemaRef; this.schemas = schemas; this.schema = schema; } @@ -56,18 +61,22 @@ public String refKey() { return key; } + public OpenapiSchema ref() + { + return schemaRef; + } public String getType() { return schema.type; } - public OpenApiSchemaView getItems() + public OpenapiSchemaView getItems() { - return schema.items == null ? null : OpenApiSchemaView.of(schemas, schema.items); + return schema.items == null ? null : OpenapiSchemaView.of(schemas, schema.items); } - public Map getProperties() + public Map getProperties() { return schema.properties; } @@ -77,10 +86,10 @@ public List getRequired() return schema.required; } - public static OpenApiSchemaView of( - Map schemas, - OpenApiSchema schema) + public static OpenapiSchemaView of( + Map schemas, + OpenapiSchema schema) { - return new OpenApiSchemaView(schemas, schema); + return new OpenapiSchemaView(schemas, schema); } } diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiServerView.java similarity index 79% rename from incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java rename to incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiServerView.java index 2a78362d66..1a238ee2da 100644 --- a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenApiServerView.java +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiServerView.java @@ -16,14 +16,14 @@ import java.net.URI; -import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenApiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiServer; -public final class OpenApiServerView +public final class OpenapiServerView { private URI url; - private OpenApiServerView( - OpenApiServer server) + private OpenapiServerView( + OpenapiServer server) { this.url = URI.create(server.url); } @@ -33,9 +33,9 @@ public URI url() return url; } - public static OpenApiServerView of( - OpenApiServer server) + public static OpenapiServerView of( + OpenapiServer server) { - return new OpenApiServerView(server); + return new OpenapiServerView(server); } } diff --git a/incubator/catalog-inline/src/main/java/io/aklivity/zilla/runtime/catalog/inline/internal/InlineCatalogHandler.java b/incubator/catalog-inline/src/main/java/io/aklivity/zilla/runtime/catalog/inline/internal/InlineCatalogHandler.java index d291d67efb..066f928be0 100644 --- a/incubator/catalog-inline/src/main/java/io/aklivity/zilla/runtime/catalog/inline/internal/InlineCatalogHandler.java +++ b/incubator/catalog-inline/src/main/java/io/aklivity/zilla/runtime/catalog/inline/internal/InlineCatalogHandler.java @@ -51,7 +51,7 @@ public int register( public String resolve( int schemaId) { - return schemas.containsKey(schemaId) ? schemas.get(schemaId) : null; + return schemas.getOrDefault(schemaId, null); } @Override @@ -60,7 +60,7 @@ public int resolve( String version) { String key = subject + version; - return schemaIds.containsKey(key) ? schemaIds.get(key) : NO_SCHEMA_ID; + return schemaIds.getOrDefault(key, NO_SCHEMA_ID); } private int generateCRC32C( diff --git a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonModelHandler.java b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonModelHandler.java index e86b7dfb3c..5b425d2bae 100644 --- a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonModelHandler.java +++ b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonModelHandler.java @@ -72,17 +72,16 @@ protected final boolean validate( int index, int length) { - boolean status = false; + boolean status = true; try { JsonProvider provider = supplyProvider(schemaId); in.wrap(buffer, index, length); provider.createReader(in).readValue(); - status = true; } catch (JsonValidatingException ex) { - ex.printStackTrace(); + status = false; } return status; } diff --git a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonReadConverterHandler.java b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonReadConverterHandler.java index ad62353542..fa78257ad6 100644 --- a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonReadConverterHandler.java +++ b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonReadConverterHandler.java @@ -65,11 +65,13 @@ private int decodePayload( } } - if (validate(schemaId, data, index, length)) + if (schemaId != NO_SCHEMA_ID && + validate(schemaId, data, index, length)) { next.accept(data, index, length); valLength = length; } + return valLength; } } diff --git a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonValidatorHandler.java b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonValidatorHandler.java index 994601abc4..445d88193e 100644 --- a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonValidatorHandler.java +++ b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonValidatorHandler.java @@ -85,7 +85,6 @@ public boolean validate( catch (JsonParsingException ex) { status = false; - ex.printStackTrace(); } return status; diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java index e8f99951e9..7b6a5fc925 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java @@ -2297,7 +2297,7 @@ private int onDecodeBody( int limit, Flyweight extension) { - boolean contentValid = exchange.validateContent(buffer, 0, limit - offset); + boolean contentValid = exchange.validateContent(buffer, offset, limit - offset); int result; if (contentValid) { From aeceb45033fa70c85022482de4fac6e5e9252007 Mon Sep 17 00:00:00 2001 From: John Fallows Date: Mon, 4 Mar 2024 09:54:56 -0800 Subject: [PATCH 24/25] Prepare release 0.9.69 --- CHANGELOG.md | 76 +++++++++++++++---- build/flyweight-maven-plugin/pom.xml | 2 +- build/pom.xml | 2 +- cloud/docker-image/pom.xml | 2 +- cloud/helm-chart/pom.xml | 2 +- cloud/pom.xml | 2 +- conf/pom.xml | 2 +- incubator/binding-amqp.spec/pom.xml | 2 +- incubator/binding-amqp/pom.xml | 2 +- incubator/binding-asyncapi.spec/pom.xml | 2 +- incubator/binding-asyncapi/pom.xml | 2 +- .../binding-openapi-asyncapi.spec/pom.xml | 2 +- incubator/binding-openapi-asyncapi/pom.xml | 2 +- incubator/binding-openapi.spec/pom.xml | 2 +- incubator/binding-openapi/pom.xml | 2 +- incubator/catalog-inline.spec/pom.xml | 2 +- incubator/catalog-inline/pom.xml | 2 +- .../catalog-schema-registry.spec/pom.xml | 2 +- incubator/catalog-schema-registry/pom.xml | 2 +- incubator/command-dump/pom.xml | 2 +- incubator/command-generate/pom.xml | 2 +- incubator/command-log/pom.xml | 2 +- incubator/command-tune/pom.xml | 2 +- incubator/exporter-otlp.spec/pom.xml | 2 +- incubator/exporter-otlp/pom.xml | 2 +- incubator/exporter-stdout.spec/pom.xml | 2 +- incubator/exporter-stdout/pom.xml | 2 +- incubator/model-avro.spec/pom.xml | 2 +- incubator/model-avro/pom.xml | 2 +- incubator/model-core.spec/pom.xml | 2 +- incubator/model-core/pom.xml | 2 +- incubator/model-json.spec/pom.xml | 2 +- incubator/model-json/pom.xml | 2 +- incubator/model-protobuf.spec/pom.xml | 2 +- incubator/model-protobuf/pom.xml | 2 +- incubator/pom.xml | 2 +- manager/pom.xml | 2 +- pom.xml | 2 +- runtime/binding-echo/pom.xml | 2 +- runtime/binding-fan/pom.xml | 2 +- runtime/binding-filesystem/pom.xml | 2 +- runtime/binding-grpc-kafka/pom.xml | 2 +- runtime/binding-grpc/pom.xml | 2 +- runtime/binding-http-filesystem/pom.xml | 2 +- runtime/binding-http-kafka/pom.xml | 2 +- runtime/binding-http/pom.xml | 2 +- runtime/binding-kafka-grpc/pom.xml | 2 +- runtime/binding-kafka/pom.xml | 2 +- runtime/binding-mqtt-kafka/pom.xml | 2 +- runtime/binding-mqtt/pom.xml | 2 +- runtime/binding-proxy/pom.xml | 2 +- runtime/binding-sse-kafka/pom.xml | 2 +- runtime/binding-sse/pom.xml | 2 +- runtime/binding-tcp/pom.xml | 2 +- runtime/binding-tls/pom.xml | 2 +- runtime/binding-ws/pom.xml | 2 +- runtime/command-metrics/pom.xml | 2 +- runtime/command-start/pom.xml | 2 +- runtime/command-stop/pom.xml | 2 +- runtime/command/pom.xml | 2 +- runtime/common/pom.xml | 2 +- runtime/engine/pom.xml | 2 +- runtime/exporter-prometheus/pom.xml | 2 +- runtime/guard-jwt/pom.xml | 2 +- runtime/metrics-grpc/pom.xml | 2 +- runtime/metrics-http/pom.xml | 2 +- runtime/metrics-stream/pom.xml | 2 +- runtime/pom.xml | 2 +- runtime/resolver-env/pom.xml | 2 +- runtime/vault-filesystem/pom.xml | 2 +- specs/binding-echo.spec/pom.xml | 2 +- specs/binding-fan.spec/pom.xml | 2 +- specs/binding-filesystem.spec/pom.xml | 2 +- specs/binding-grpc-kafka.spec/pom.xml | 2 +- specs/binding-grpc.spec/pom.xml | 2 +- specs/binding-http-filesystem.spec/pom.xml | 2 +- specs/binding-http-kafka.spec/pom.xml | 2 +- specs/binding-http.spec/pom.xml | 2 +- specs/binding-kafka-grpc.spec/pom.xml | 2 +- specs/binding-kafka.spec/pom.xml | 2 +- specs/binding-mqtt-kafka.spec/pom.xml | 2 +- specs/binding-mqtt.spec/pom.xml | 2 +- specs/binding-proxy.spec/pom.xml | 2 +- specs/binding-sse-kafka.spec/pom.xml | 2 +- specs/binding-sse.spec/pom.xml | 2 +- specs/binding-tcp.spec/pom.xml | 2 +- specs/binding-tls.spec/pom.xml | 2 +- specs/binding-ws.spec/pom.xml | 2 +- specs/engine.spec/pom.xml | 2 +- specs/exporter-prometheus.spec/pom.xml | 2 +- specs/guard-jwt.spec/pom.xml | 2 +- specs/metrics-grpc.spec/pom.xml | 2 +- specs/metrics-http.spec/pom.xml | 2 +- specs/metrics-stream.spec/pom.xml | 2 +- specs/pom.xml | 2 +- specs/vault-filesystem.spec/pom.xml | 2 +- 96 files changed, 158 insertions(+), 108 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 801425c161..e6ab01255c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Changelog +## [Unreleased](https://github.com/aklivity/zilla/tree/HEAD) + +[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.68...HEAD) + +**Implemented enhancements:** + +- Support parameters in KafkaTopicsConfig [\#809](https://github.com/aklivity/zilla/pull/809) ([bmaidics](https://github.com/bmaidics)) + +**Fixed bugs:** + +- SEVERE: Problem adapting object of type class NamespaceConfig to interface jakarta.json.JsonObject in class class NamespaceAdapter [\#796](https://github.com/aklivity/zilla/issues/796) +- Zilla is validating `env` vars before replacing them. [\#795](https://github.com/aklivity/zilla/issues/795) +- Basic Docker Compose Setup Clogs CPU With Error Messages [\#722](https://github.com/aklivity/zilla/issues/722) + +**Closed issues:** + +- Use dedicated env var to enable Incubator features [\#800](https://github.com/aklivity/zilla/issues/800) +- Support `http` to `kafka` proxy using `openapi.yaml` and `asyncapi.yaml` [\#742](https://github.com/aklivity/zilla/issues/742) +- Support `mqtt` to `kafka` proxy using `asyncapi.yaml` [\#741](https://github.com/aklivity/zilla/issues/741) +- Support `openapi` `http` proxy using `openapi.yaml` [\#740](https://github.com/aklivity/zilla/issues/740) +- Support `asyncapi` `http` proxy using `asyncapi.yaml` [\#739](https://github.com/aklivity/zilla/issues/739) +- Support `asyncapi` `mqtt` proxy using `asyncapi.yaml` [\#738](https://github.com/aklivity/zilla/issues/738) +- Support local logging of events caused by external actors [\#679](https://github.com/aklivity/zilla/issues/679) + +**Merged pull requests:** + +- Asyncapi and Openapi bug fixes [\#826](https://github.com/aklivity/zilla/pull/826) ([akrambek](https://github.com/akrambek)) +- Asyncapi catalog implementation [\#825](https://github.com/aklivity/zilla/pull/825) ([bmaidics](https://github.com/bmaidics)) +- Fix NPE in KafkaSignalStream [\#823](https://github.com/aklivity/zilla/pull/823) ([bmaidics](https://github.com/bmaidics)) +- Fix early flush sending for retained stream [\#822](https://github.com/aklivity/zilla/pull/822) ([bmaidics](https://github.com/bmaidics)) +- Add incubating annotation for stdout exporter [\#819](https://github.com/aklivity/zilla/pull/819) ([jfallows](https://github.com/jfallows)) +- MQTT-Kafka asyncapi proxy [\#818](https://github.com/aklivity/zilla/pull/818) ([bmaidics](https://github.com/bmaidics)) +- Fix kafka client composite resolvedId [\#816](https://github.com/aklivity/zilla/pull/816) ([bmaidics](https://github.com/bmaidics)) +- Use env var to add incubator java option [\#811](https://github.com/aklivity/zilla/pull/811) ([vordimous](https://github.com/vordimous)) +- Support http to kafka proxy using openapi.yaml and asyncapi.yaml [\#810](https://github.com/aklivity/zilla/pull/810) ([akrambek](https://github.com/akrambek)) +- Structured models require `catalog` config [\#807](https://github.com/aklivity/zilla/pull/807) ([aDaemonThread](https://github.com/aDaemonThread)) +- Include qualified vault name on binding [\#806](https://github.com/aklivity/zilla/pull/806) ([jfallows](https://github.com/jfallows)) +- Include config exception cause [\#805](https://github.com/aklivity/zilla/pull/805) ([jfallows](https://github.com/jfallows)) +- Kafka asyncapi client [\#804](https://github.com/aklivity/zilla/pull/804) ([bmaidics](https://github.com/bmaidics)) +- Support k3po ephemeral option [\#801](https://github.com/aklivity/zilla/pull/801) ([akrambek](https://github.com/akrambek)) +- Support asyncapi http proxy using asyncapi.yaml [\#799](https://github.com/aklivity/zilla/pull/799) ([bmaidics](https://github.com/bmaidics)) +- Fix kafka sasl schema validation to support expressions [\#798](https://github.com/aklivity/zilla/pull/798) ([akrambek](https://github.com/akrambek)) +- Zilla is validating env vars before replacing them [\#797](https://github.com/aklivity/zilla/pull/797) ([akrambek](https://github.com/akrambek)) +- Support openapi http proxy using openapi.yaml [\#778](https://github.com/aklivity/zilla/pull/778) ([akrambek](https://github.com/akrambek)) +- Support asyncapi mqtt proxy using asyncapi.yaml [\#764](https://github.com/aklivity/zilla/pull/764) ([bmaidics](https://github.com/bmaidics)) +- Support local logging of events [\#755](https://github.com/aklivity/zilla/pull/755) ([attilakreiner](https://github.com/attilakreiner)) + ## [0.9.68](https://github.com/aklivity/zilla/tree/0.9.68) (2024-02-13) [Full Changelog](https://github.com/aklivity/zilla/compare/0.9.67...0.9.68) @@ -19,10 +66,6 @@ **Implemented enhancements:** - Use `model` and `view` when describing the message type [\#750](https://github.com/aklivity/zilla/issues/750) -- Support obtaining `protobuf` schemas from `schema registry` for `grpc` services [\#697](https://github.com/aklivity/zilla/issues/697) -- Support idempotent `mqtt` `qos 2` publish to `kafka` [\#677](https://github.com/aklivity/zilla/issues/677) -- Detect and inspect invalid messages received [\#676](https://github.com/aklivity/zilla/issues/676) -- Support incremental validation of fragmented messages sent by client [\#671](https://github.com/aklivity/zilla/issues/671) - Catalog cache TTL implementation [\#658](https://github.com/aklivity/zilla/pull/658) ([aDaemonThread](https://github.com/aDaemonThread)) **Fixed bugs:** @@ -35,6 +78,13 @@ - Fix zilla crash when it tries to send flush on retain stream [\#784](https://github.com/aklivity/zilla/pull/784) ([bmaidics](https://github.com/bmaidics)) - Limit sharding to mqtt 5 [\#760](https://github.com/aklivity/zilla/pull/760) ([bmaidics](https://github.com/bmaidics)) +**Closed issues:** + +- Support obtaining `protobuf` schemas from `schema registry` for `grpc` services [\#697](https://github.com/aklivity/zilla/issues/697) +- Support idempotent `mqtt` `qos 2` publish to `kafka` [\#677](https://github.com/aklivity/zilla/issues/677) +- Detect and inspect invalid messages received [\#676](https://github.com/aklivity/zilla/issues/676) +- Support incremental validation of fragmented messages sent by client [\#671](https://github.com/aklivity/zilla/issues/671) + **Merged pull requests:** - Simplify TLSv1.3 handshake check [\#792](https://github.com/aklivity/zilla/pull/792) ([jfallows](https://github.com/jfallows)) @@ -69,16 +119,16 @@ [Full Changelog](https://github.com/aklivity/zilla/compare/0.9.65...0.9.66) -**Implemented enhancements:** +**Fixed bugs:** + +- Schema validation fails before the `${{env.*}}` parameters have been removed [\#583](https://github.com/aklivity/zilla/issues/583) + +**Closed issues:** - Support `openapi` `http` response validation [\#684](https://github.com/aklivity/zilla/issues/684) - Support `protobuf` conversion to and from `json` for `kafka` messages [\#682](https://github.com/aklivity/zilla/issues/682) - Support incubator features preview in zilla release docker image [\#670](https://github.com/aklivity/zilla/issues/670) -**Fixed bugs:** - -- Schema validation fails before the `${{env.*}}` parameters have been removed [\#583](https://github.com/aklivity/zilla/issues/583) - **Merged pull requests:** - update license exclude path to include both zpmw files [\#759](https://github.com/aklivity/zilla/pull/759) ([vordimous](https://github.com/vordimous)) @@ -94,10 +144,6 @@ **Implemented enhancements:** -- Support `avro` conversion to and from `json` for `kafka` messages [\#681](https://github.com/aklivity/zilla/issues/681) -- Support observability of zilla engine internal streams [\#678](https://github.com/aklivity/zilla/issues/678) -- Simplify configuration of multiple protocols on different tcp ports [\#669](https://github.com/aklivity/zilla/issues/669) -- Simplify kafka client bootstrap server names and ports config [\#619](https://github.com/aklivity/zilla/issues/619) - MQTT publish QoS 2 as Kafka produce with acks in\_sync\_replicas and idempotent `producerId` [\#605](https://github.com/aklivity/zilla/issues/605) - Add the option to route by `port` in the `tls` binding [\#564](https://github.com/aklivity/zilla/issues/564) - Support outbound message transformation from `protobuf` to `json` [\#458](https://github.com/aklivity/zilla/issues/458) @@ -128,6 +174,10 @@ **Closed issues:** - Prototype composite binding support with nested namespaces [\#685](https://github.com/aklivity/zilla/issues/685) +- Support `avro` conversion to and from `json` for `kafka` messages [\#681](https://github.com/aklivity/zilla/issues/681) +- Support observability of zilla engine internal streams [\#678](https://github.com/aklivity/zilla/issues/678) +- Simplify configuration of multiple protocols on different tcp ports [\#669](https://github.com/aklivity/zilla/issues/669) +- Simplify kafka client bootstrap server names and ports config [\#619](https://github.com/aklivity/zilla/issues/619) - Build has been failed in local [\#229](https://github.com/aklivity/zilla/issues/229) **Merged pull requests:** diff --git a/build/flyweight-maven-plugin/pom.xml b/build/flyweight-maven-plugin/pom.xml index 74d6a13033..fad482be76 100644 --- a/build/flyweight-maven-plugin/pom.xml +++ b/build/flyweight-maven-plugin/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla build - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/build/pom.xml b/build/pom.xml index 99a8e7a8fa..9287417e6b 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index 6509f55e19..801505b541 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla cloud - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/cloud/helm-chart/pom.xml b/cloud/helm-chart/pom.xml index 853e06803a..19fe35c8f9 100644 --- a/cloud/helm-chart/pom.xml +++ b/cloud/helm-chart/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla cloud - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/cloud/pom.xml b/cloud/pom.xml index 68c51f05c8..be5d7b7ede 100644 --- a/cloud/pom.xml +++ b/cloud/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/conf/pom.xml b/conf/pom.xml index 86622b6574..a4e6729542 100644 --- a/conf/pom.xml +++ b/conf/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/binding-amqp.spec/pom.xml b/incubator/binding-amqp.spec/pom.xml index 40846d1a35..f502c6e2ef 100644 --- a/incubator/binding-amqp.spec/pom.xml +++ b/incubator/binding-amqp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/binding-amqp/pom.xml b/incubator/binding-amqp/pom.xml index 71c9c61d90..7841077e88 100644 --- a/incubator/binding-amqp/pom.xml +++ b/incubator/binding-amqp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/binding-asyncapi.spec/pom.xml b/incubator/binding-asyncapi.spec/pom.xml index 81df8c7969..1a20bb45c8 100644 --- a/incubator/binding-asyncapi.spec/pom.xml +++ b/incubator/binding-asyncapi.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/binding-asyncapi/pom.xml b/incubator/binding-asyncapi/pom.xml index cd85fcc214..b7b34f4780 100644 --- a/incubator/binding-asyncapi/pom.xml +++ b/incubator/binding-asyncapi/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/binding-openapi-asyncapi.spec/pom.xml b/incubator/binding-openapi-asyncapi.spec/pom.xml index 3157d47c92..deed770da7 100644 --- a/incubator/binding-openapi-asyncapi.spec/pom.xml +++ b/incubator/binding-openapi-asyncapi.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/binding-openapi-asyncapi/pom.xml b/incubator/binding-openapi-asyncapi/pom.xml index e0faa31bae..e717f0f0de 100644 --- a/incubator/binding-openapi-asyncapi/pom.xml +++ b/incubator/binding-openapi-asyncapi/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/binding-openapi.spec/pom.xml b/incubator/binding-openapi.spec/pom.xml index 022e246172..f1bb993a8d 100644 --- a/incubator/binding-openapi.spec/pom.xml +++ b/incubator/binding-openapi.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/binding-openapi/pom.xml b/incubator/binding-openapi/pom.xml index 0d4ce30338..b517b8d405 100644 --- a/incubator/binding-openapi/pom.xml +++ b/incubator/binding-openapi/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/catalog-inline.spec/pom.xml b/incubator/catalog-inline.spec/pom.xml index e6494bfdc2..6f2dfa4bbf 100644 --- a/incubator/catalog-inline.spec/pom.xml +++ b/incubator/catalog-inline.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/catalog-inline/pom.xml b/incubator/catalog-inline/pom.xml index d96f0e1b74..0dcc934199 100644 --- a/incubator/catalog-inline/pom.xml +++ b/incubator/catalog-inline/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/catalog-schema-registry.spec/pom.xml b/incubator/catalog-schema-registry.spec/pom.xml index 85398279c4..f2016b5ee3 100644 --- a/incubator/catalog-schema-registry.spec/pom.xml +++ b/incubator/catalog-schema-registry.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/catalog-schema-registry/pom.xml b/incubator/catalog-schema-registry/pom.xml index 22c39b3d02..461e4292e2 100644 --- a/incubator/catalog-schema-registry/pom.xml +++ b/incubator/catalog-schema-registry/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/command-dump/pom.xml b/incubator/command-dump/pom.xml index f74b1d239a..d63f01abcc 100644 --- a/incubator/command-dump/pom.xml +++ b/incubator/command-dump/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/command-generate/pom.xml b/incubator/command-generate/pom.xml index 3ea2a400e7..aa3ea83e14 100644 --- a/incubator/command-generate/pom.xml +++ b/incubator/command-generate/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/command-log/pom.xml b/incubator/command-log/pom.xml index 12b17def9c..4fbf297c72 100644 --- a/incubator/command-log/pom.xml +++ b/incubator/command-log/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/command-tune/pom.xml b/incubator/command-tune/pom.xml index 7374c89ed9..27bd604857 100644 --- a/incubator/command-tune/pom.xml +++ b/incubator/command-tune/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/exporter-otlp.spec/pom.xml b/incubator/exporter-otlp.spec/pom.xml index c23892479f..4aec1f1e66 100644 --- a/incubator/exporter-otlp.spec/pom.xml +++ b/incubator/exporter-otlp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/exporter-otlp/pom.xml b/incubator/exporter-otlp/pom.xml index 48b162cc1b..9900771412 100644 --- a/incubator/exporter-otlp/pom.xml +++ b/incubator/exporter-otlp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/exporter-stdout.spec/pom.xml b/incubator/exporter-stdout.spec/pom.xml index 1b07db88bf..af5ec8f1ec 100644 --- a/incubator/exporter-stdout.spec/pom.xml +++ b/incubator/exporter-stdout.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/exporter-stdout/pom.xml b/incubator/exporter-stdout/pom.xml index 58bf155c90..6621d1ce6a 100644 --- a/incubator/exporter-stdout/pom.xml +++ b/incubator/exporter-stdout/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/model-avro.spec/pom.xml b/incubator/model-avro.spec/pom.xml index 764ad4a5b7..f9321934dc 100644 --- a/incubator/model-avro.spec/pom.xml +++ b/incubator/model-avro.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/model-avro/pom.xml b/incubator/model-avro/pom.xml index 3d32bce7ec..4def40572a 100644 --- a/incubator/model-avro/pom.xml +++ b/incubator/model-avro/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/model-core.spec/pom.xml b/incubator/model-core.spec/pom.xml index 91932f47c8..f63fcc5c0b 100644 --- a/incubator/model-core.spec/pom.xml +++ b/incubator/model-core.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/model-core/pom.xml b/incubator/model-core/pom.xml index 466f9234e9..9c9d5a9f68 100644 --- a/incubator/model-core/pom.xml +++ b/incubator/model-core/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/model-json.spec/pom.xml b/incubator/model-json.spec/pom.xml index c1d9a96e96..06ed66555d 100644 --- a/incubator/model-json.spec/pom.xml +++ b/incubator/model-json.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/model-json/pom.xml b/incubator/model-json/pom.xml index 7fe90e569b..686ed0b5b0 100644 --- a/incubator/model-json/pom.xml +++ b/incubator/model-json/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/model-protobuf.spec/pom.xml b/incubator/model-protobuf.spec/pom.xml index d30c6ab3ba..db634b1928 100644 --- a/incubator/model-protobuf.spec/pom.xml +++ b/incubator/model-protobuf.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/model-protobuf/pom.xml b/incubator/model-protobuf/pom.xml index d0e9b05ecd..38c7e07ce4 100644 --- a/incubator/model-protobuf/pom.xml +++ b/incubator/model-protobuf/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/incubator/pom.xml b/incubator/pom.xml index 806db5cc6f..ee34ff9d53 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/manager/pom.xml b/manager/pom.xml index 8d8a444944..55c2a71217 100644 --- a/manager/pom.xml +++ b/manager/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/pom.xml b/pom.xml index bda18f1e6f..a3fe52c36c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 4.0.0 io.aklivity.zilla zilla - develop-SNAPSHOT + 0.9.69 pom zilla https://github.com/aklivity/zilla diff --git a/runtime/binding-echo/pom.xml b/runtime/binding-echo/pom.xml index 37bc15776e..461b9066d7 100644 --- a/runtime/binding-echo/pom.xml +++ b/runtime/binding-echo/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-fan/pom.xml b/runtime/binding-fan/pom.xml index 77b5e0aab5..799039a1d7 100644 --- a/runtime/binding-fan/pom.xml +++ b/runtime/binding-fan/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-filesystem/pom.xml b/runtime/binding-filesystem/pom.xml index 46b6e08957..4534105606 100644 --- a/runtime/binding-filesystem/pom.xml +++ b/runtime/binding-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-grpc-kafka/pom.xml b/runtime/binding-grpc-kafka/pom.xml index a9952b0a68..412de9c613 100644 --- a/runtime/binding-grpc-kafka/pom.xml +++ b/runtime/binding-grpc-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-grpc/pom.xml b/runtime/binding-grpc/pom.xml index 8bf0b5f396..a98b6d6f85 100644 --- a/runtime/binding-grpc/pom.xml +++ b/runtime/binding-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-http-filesystem/pom.xml b/runtime/binding-http-filesystem/pom.xml index 19d46db62e..3205e3f980 100644 --- a/runtime/binding-http-filesystem/pom.xml +++ b/runtime/binding-http-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-http-kafka/pom.xml b/runtime/binding-http-kafka/pom.xml index dabc54fb58..928e95b6ee 100644 --- a/runtime/binding-http-kafka/pom.xml +++ b/runtime/binding-http-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-http/pom.xml b/runtime/binding-http/pom.xml index 15111be380..1180b24d2b 100644 --- a/runtime/binding-http/pom.xml +++ b/runtime/binding-http/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-kafka-grpc/pom.xml b/runtime/binding-kafka-grpc/pom.xml index 41b94c3bcb..354fec2fe7 100644 --- a/runtime/binding-kafka-grpc/pom.xml +++ b/runtime/binding-kafka-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-kafka/pom.xml b/runtime/binding-kafka/pom.xml index d643324d07..2e3c7ba66e 100644 --- a/runtime/binding-kafka/pom.xml +++ b/runtime/binding-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-mqtt-kafka/pom.xml b/runtime/binding-mqtt-kafka/pom.xml index 78e8e17d68..72450c8e77 100644 --- a/runtime/binding-mqtt-kafka/pom.xml +++ b/runtime/binding-mqtt-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-mqtt/pom.xml b/runtime/binding-mqtt/pom.xml index b349482243..a538705948 100644 --- a/runtime/binding-mqtt/pom.xml +++ b/runtime/binding-mqtt/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-proxy/pom.xml b/runtime/binding-proxy/pom.xml index 38985b6961..7cc9e5fdfb 100644 --- a/runtime/binding-proxy/pom.xml +++ b/runtime/binding-proxy/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-sse-kafka/pom.xml b/runtime/binding-sse-kafka/pom.xml index 73486efd4d..3599cc0ff0 100644 --- a/runtime/binding-sse-kafka/pom.xml +++ b/runtime/binding-sse-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-sse/pom.xml b/runtime/binding-sse/pom.xml index 1d7aa891b8..c9a0939f04 100644 --- a/runtime/binding-sse/pom.xml +++ b/runtime/binding-sse/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-tcp/pom.xml b/runtime/binding-tcp/pom.xml index 513e5eae6b..e15efd6f4a 100644 --- a/runtime/binding-tcp/pom.xml +++ b/runtime/binding-tcp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-tls/pom.xml b/runtime/binding-tls/pom.xml index 1fd6bcd4d1..69930cae90 100644 --- a/runtime/binding-tls/pom.xml +++ b/runtime/binding-tls/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/binding-ws/pom.xml b/runtime/binding-ws/pom.xml index 6f846d1a83..65d1a3b785 100644 --- a/runtime/binding-ws/pom.xml +++ b/runtime/binding-ws/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/command-metrics/pom.xml b/runtime/command-metrics/pom.xml index ad52828b76..402371b5c9 100644 --- a/runtime/command-metrics/pom.xml +++ b/runtime/command-metrics/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/command-start/pom.xml b/runtime/command-start/pom.xml index 8ab765d93d..2984880454 100644 --- a/runtime/command-start/pom.xml +++ b/runtime/command-start/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/command-stop/pom.xml b/runtime/command-stop/pom.xml index 9334bc51e1..5cb9763d9e 100644 --- a/runtime/command-stop/pom.xml +++ b/runtime/command-stop/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/command/pom.xml b/runtime/command/pom.xml index 29712783a0..72e8a3d401 100644 --- a/runtime/command/pom.xml +++ b/runtime/command/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/common/pom.xml b/runtime/common/pom.xml index a96a6c1cab..c979684c52 100644 --- a/runtime/common/pom.xml +++ b/runtime/common/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/engine/pom.xml b/runtime/engine/pom.xml index ccea03c210..952bbe989d 100644 --- a/runtime/engine/pom.xml +++ b/runtime/engine/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/exporter-prometheus/pom.xml b/runtime/exporter-prometheus/pom.xml index dad973cdb8..71a9c59526 100644 --- a/runtime/exporter-prometheus/pom.xml +++ b/runtime/exporter-prometheus/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/guard-jwt/pom.xml b/runtime/guard-jwt/pom.xml index fa3544628a..414415cc0d 100644 --- a/runtime/guard-jwt/pom.xml +++ b/runtime/guard-jwt/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/metrics-grpc/pom.xml b/runtime/metrics-grpc/pom.xml index 5713263b66..d3270ea8a6 100644 --- a/runtime/metrics-grpc/pom.xml +++ b/runtime/metrics-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/metrics-http/pom.xml b/runtime/metrics-http/pom.xml index f505adfd82..09e2e07a8d 100644 --- a/runtime/metrics-http/pom.xml +++ b/runtime/metrics-http/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/metrics-stream/pom.xml b/runtime/metrics-stream/pom.xml index 9ccb8e23dd..9cf644501d 100644 --- a/runtime/metrics-stream/pom.xml +++ b/runtime/metrics-stream/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/pom.xml b/runtime/pom.xml index 72440d2110..dad679a4cb 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/resolver-env/pom.xml b/runtime/resolver-env/pom.xml index a7c5e5d4a5..6f17107b41 100644 --- a/runtime/resolver-env/pom.xml +++ b/runtime/resolver-env/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/runtime/vault-filesystem/pom.xml b/runtime/vault-filesystem/pom.xml index d5ab82cb01..15be96fab0 100644 --- a/runtime/vault-filesystem/pom.xml +++ b/runtime/vault-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-echo.spec/pom.xml b/specs/binding-echo.spec/pom.xml index d0bfed636e..64f61c8a02 100644 --- a/specs/binding-echo.spec/pom.xml +++ b/specs/binding-echo.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-fan.spec/pom.xml b/specs/binding-fan.spec/pom.xml index 027d3210d7..7818e78016 100644 --- a/specs/binding-fan.spec/pom.xml +++ b/specs/binding-fan.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-filesystem.spec/pom.xml b/specs/binding-filesystem.spec/pom.xml index cc20c4134b..b24aac0741 100644 --- a/specs/binding-filesystem.spec/pom.xml +++ b/specs/binding-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-grpc-kafka.spec/pom.xml b/specs/binding-grpc-kafka.spec/pom.xml index 546b34371a..59ca15086a 100644 --- a/specs/binding-grpc-kafka.spec/pom.xml +++ b/specs/binding-grpc-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-grpc.spec/pom.xml b/specs/binding-grpc.spec/pom.xml index c75c35e62e..2c616de8fb 100644 --- a/specs/binding-grpc.spec/pom.xml +++ b/specs/binding-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-http-filesystem.spec/pom.xml b/specs/binding-http-filesystem.spec/pom.xml index b9499ae553..6ae883ea03 100644 --- a/specs/binding-http-filesystem.spec/pom.xml +++ b/specs/binding-http-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-http-kafka.spec/pom.xml b/specs/binding-http-kafka.spec/pom.xml index 456a3106b5..8b32779d21 100644 --- a/specs/binding-http-kafka.spec/pom.xml +++ b/specs/binding-http-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-http.spec/pom.xml b/specs/binding-http.spec/pom.xml index 38a7898be9..5c7806fb58 100644 --- a/specs/binding-http.spec/pom.xml +++ b/specs/binding-http.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-kafka-grpc.spec/pom.xml b/specs/binding-kafka-grpc.spec/pom.xml index 197265531f..4428a0d9cf 100644 --- a/specs/binding-kafka-grpc.spec/pom.xml +++ b/specs/binding-kafka-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-kafka.spec/pom.xml b/specs/binding-kafka.spec/pom.xml index c4559fdb23..508d080b0f 100644 --- a/specs/binding-kafka.spec/pom.xml +++ b/specs/binding-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-mqtt-kafka.spec/pom.xml b/specs/binding-mqtt-kafka.spec/pom.xml index 517559d0d0..bd7148400b 100644 --- a/specs/binding-mqtt-kafka.spec/pom.xml +++ b/specs/binding-mqtt-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-mqtt.spec/pom.xml b/specs/binding-mqtt.spec/pom.xml index f624bb85ae..207aa18298 100644 --- a/specs/binding-mqtt.spec/pom.xml +++ b/specs/binding-mqtt.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-proxy.spec/pom.xml b/specs/binding-proxy.spec/pom.xml index fcdb4b9723..ebbe76284f 100644 --- a/specs/binding-proxy.spec/pom.xml +++ b/specs/binding-proxy.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-sse-kafka.spec/pom.xml b/specs/binding-sse-kafka.spec/pom.xml index 20b099b140..0f862462a7 100644 --- a/specs/binding-sse-kafka.spec/pom.xml +++ b/specs/binding-sse-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-sse.spec/pom.xml b/specs/binding-sse.spec/pom.xml index 5cf8e9018c..4b1c4226a6 100644 --- a/specs/binding-sse.spec/pom.xml +++ b/specs/binding-sse.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-tcp.spec/pom.xml b/specs/binding-tcp.spec/pom.xml index 94ef266ac7..49e432089b 100644 --- a/specs/binding-tcp.spec/pom.xml +++ b/specs/binding-tcp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-tls.spec/pom.xml b/specs/binding-tls.spec/pom.xml index d38dbf81e7..27ec2ca9e6 100644 --- a/specs/binding-tls.spec/pom.xml +++ b/specs/binding-tls.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/binding-ws.spec/pom.xml b/specs/binding-ws.spec/pom.xml index d7f9373844..157e53b353 100644 --- a/specs/binding-ws.spec/pom.xml +++ b/specs/binding-ws.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/engine.spec/pom.xml b/specs/engine.spec/pom.xml index 1d5bd3dafe..3dc193021c 100644 --- a/specs/engine.spec/pom.xml +++ b/specs/engine.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/exporter-prometheus.spec/pom.xml b/specs/exporter-prometheus.spec/pom.xml index ab815f1981..3dbf6f0648 100644 --- a/specs/exporter-prometheus.spec/pom.xml +++ b/specs/exporter-prometheus.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/guard-jwt.spec/pom.xml b/specs/guard-jwt.spec/pom.xml index ab5d123aed..d643c45e99 100644 --- a/specs/guard-jwt.spec/pom.xml +++ b/specs/guard-jwt.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/metrics-grpc.spec/pom.xml b/specs/metrics-grpc.spec/pom.xml index 884c0b1751..545584affb 100644 --- a/specs/metrics-grpc.spec/pom.xml +++ b/specs/metrics-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/metrics-http.spec/pom.xml b/specs/metrics-http.spec/pom.xml index 7d6afa3aaa..5889746ced 100644 --- a/specs/metrics-http.spec/pom.xml +++ b/specs/metrics-http.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/metrics-stream.spec/pom.xml b/specs/metrics-stream.spec/pom.xml index 5d51e42f16..684031789a 100644 --- a/specs/metrics-stream.spec/pom.xml +++ b/specs/metrics-stream.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/pom.xml b/specs/pom.xml index 5979459274..c5117a86ee 100644 --- a/specs/pom.xml +++ b/specs/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - develop-SNAPSHOT + 0.9.69 ../pom.xml diff --git a/specs/vault-filesystem.spec/pom.xml b/specs/vault-filesystem.spec/pom.xml index b248001b27..a1ad40231c 100644 --- a/specs/vault-filesystem.spec/pom.xml +++ b/specs/vault-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - develop-SNAPSHOT + 0.9.69 ../pom.xml From 726f1fde923b80bc8fd01acc3eebe2f89d2a325d Mon Sep 17 00:00:00 2001 From: John Fallows Date: Mon, 4 Mar 2024 09:57:33 -0800 Subject: [PATCH 25/25] Fix parent pom reference with relative path --- incubator/exporter-stdout.spec/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/incubator/exporter-stdout.spec/pom.xml b/incubator/exporter-stdout.spec/pom.xml index af5ec8f1ec..1ec0a89dd1 100644 --- a/incubator/exporter-stdout.spec/pom.xml +++ b/incubator/exporter-stdout.spec/pom.xml @@ -7,7 +7,7 @@ 4.0.0 io.aklivity.zilla - specs + incubator 0.9.69 ../pom.xml