From 7b6b663e8d4cd6c5ac1aa694a805d084fd665149 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 23 May 2024 16:06:47 +0200 Subject: [PATCH] Vert.x: support Kotlin suspend functions in event bus consumers --- bom/application/pom.xml | 15 +++ devtools/bom-descriptor-json/pom.xml | 13 ++ docs/pom.xml | 13 ++ docs/src/main/asciidoc/kotlin.adoc | 3 + extensions/kotlin/deployment/pom.xml | 5 + extensions/kotlin/runtime/pom.xml | 6 + extensions/vertx/deployment-spi/pom.xml | 26 ++++ ...entConsumerInvokerCustomizerBuildItem.java | 24 ++++ extensions/vertx/deployment/pom.xml | 4 + .../vertx/deployment/VertxProcessor.java | 30 +++-- extensions/vertx/kotlin/deployment/pom.xml | 52 ++++++++ .../deployment/VertxKotlinProcessor.java | 48 +++++++ extensions/vertx/kotlin/pom.xml | 19 +++ extensions/vertx/kotlin/runtime/pom.xml | 121 ++++++++++++++++++ .../runtime/ApplicationCoroutineScope.kt | 18 +++ .../vertx/kotlin/runtime/CoroutineInvoker.kt | 36 ++++++ .../vertx/kotlin/runtime/VertxDispatcher.kt | 24 ++++ .../resources/META-INF/quarkus-extension.yaml | 5 + extensions/vertx/pom.xml | 2 + integration-tests/pom.xml | 1 + integration-tests/vertx-kotlin/pom.xml | 103 +++++++++++++++ .../it/vertx/kotlin/MessageConsumers.kt | 49 +++++++ .../vertx/kotlin/SuspendMessageConsumers.kt | 55 ++++++++ .../it/vertx/kotlin/KotlinConsumeEventTest.kt | 62 +++++++++ .../kotlin/KotlinSuspendConsumeEventTest.kt | 62 +++++++++ 25 files changed, 788 insertions(+), 8 deletions(-) create mode 100644 extensions/vertx/deployment-spi/pom.xml create mode 100644 extensions/vertx/deployment-spi/src/main/java/io/quarkus/vertx/deployment/spi/EventConsumerInvokerCustomizerBuildItem.java create mode 100644 extensions/vertx/kotlin/deployment/pom.xml create mode 100644 extensions/vertx/kotlin/deployment/src/main/java/io/quarkus/vertx/kotlin/deployment/VertxKotlinProcessor.java create mode 100644 extensions/vertx/kotlin/pom.xml create mode 100644 extensions/vertx/kotlin/runtime/pom.xml create mode 100644 extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/ApplicationCoroutineScope.kt create mode 100644 extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/CoroutineInvoker.kt create mode 100644 extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/VertxDispatcher.kt create mode 100644 extensions/vertx/kotlin/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 integration-tests/vertx-kotlin/pom.xml create mode 100644 integration-tests/vertx-kotlin/src/main/kotlin/io/quarkus/it/vertx/kotlin/MessageConsumers.kt create mode 100644 integration-tests/vertx-kotlin/src/main/kotlin/io/quarkus/it/vertx/kotlin/SuspendMessageConsumers.kt create mode 100644 integration-tests/vertx-kotlin/src/test/kotlin/io/quarkus/it/vertx/kotlin/KotlinConsumeEventTest.kt create mode 100644 integration-tests/vertx-kotlin/src/test/kotlin/io/quarkus/it/vertx/kotlin/KotlinSuspendConsumeEventTest.kt diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 81afa4ffbfee5f..fdca98230663dd 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -1897,6 +1897,11 @@ quarkus-vertx-deployment ${project.version} + + io.quarkus + quarkus-vertx-deployment-spi + ${project.version} + io.quarkus quarkus-vertx-latebound-mdc-provider @@ -1942,6 +1947,16 @@ quarkus-vertx-http-dev-ui-resources ${project.version} + + io.quarkus + quarkus-vertx-kotlin + ${project.version} + + + io.quarkus + quarkus-vertx-kotlin-deployment + ${project.version} + io.quarkus diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml index ffc75fd5220238..c9b4761dbcffe1 100644 --- a/devtools/bom-descriptor-json/pom.xml +++ b/devtools/bom-descriptor-json/pom.xml @@ -2930,6 +2930,19 @@ + + io.quarkus + quarkus-vertx-kotlin + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-virtual-threads diff --git a/docs/pom.xml b/docs/pom.xml index 6ee804140ed8ef..430c926c37b40a 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -2942,6 +2942,19 @@ + + io.quarkus + quarkus-vertx-kotlin-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-virtual-threads-deployment diff --git a/docs/src/main/asciidoc/kotlin.adoc b/docs/src/main/asciidoc/kotlin.adoc index eb2710ebd3347a..84e2e61be86fd3 100644 --- a/docs/src/main/asciidoc/kotlin.adoc +++ b/docs/src/main/asciidoc/kotlin.adoc @@ -501,6 +501,9 @@ The following extensions provide support for Kotlin Coroutines by allowing the u |`quarkus-smallrye-fault-tolerance` |Support is provided for the declarative annotation-based API +|`quarkus-vertx` +|Support is provided for `@ConsumeEvent` methods + |=== === Kotlin coroutines and Mutiny diff --git a/extensions/kotlin/deployment/pom.xml b/extensions/kotlin/deployment/pom.xml index 6e931d801d6275..9515f8f899c833 100644 --- a/extensions/kotlin/deployment/pom.xml +++ b/extensions/kotlin/deployment/pom.xml @@ -28,6 +28,11 @@ kotlin-compiler + + io.quarkus + quarkus-vertx-kotlin-deployment + true + diff --git a/extensions/kotlin/runtime/pom.xml b/extensions/kotlin/runtime/pom.xml index a92dd5751b3763..7af7abdf3c66c1 100644 --- a/extensions/kotlin/runtime/pom.xml +++ b/extensions/kotlin/runtime/pom.xml @@ -78,5 +78,11 @@ org.jetbrains.kotlinx kotlinx-coroutines-jdk8 + + + io.quarkus + quarkus-vertx-kotlin + true + diff --git a/extensions/vertx/deployment-spi/pom.xml b/extensions/vertx/deployment-spi/pom.xml new file mode 100644 index 00000000000000..719736a3d4e08c --- /dev/null +++ b/extensions/vertx/deployment-spi/pom.xml @@ -0,0 +1,26 @@ + + + + quarkus-vertx-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-vertx-deployment-spi + Quarkus - Vert.x - Deployment - SPI + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus.arc + arc-processor + + + + diff --git a/extensions/vertx/deployment-spi/src/main/java/io/quarkus/vertx/deployment/spi/EventConsumerInvokerCustomizerBuildItem.java b/extensions/vertx/deployment-spi/src/main/java/io/quarkus/vertx/deployment/spi/EventConsumerInvokerCustomizerBuildItem.java new file mode 100644 index 00000000000000..20f4f993409d27 --- /dev/null +++ b/extensions/vertx/deployment-spi/src/main/java/io/quarkus/vertx/deployment/spi/EventConsumerInvokerCustomizerBuildItem.java @@ -0,0 +1,24 @@ +package io.quarkus.vertx.deployment.spi; + +import java.util.function.BiConsumer; + +import org.jboss.jandex.MethodInfo; + +import io.quarkus.arc.processor.InvokerBuilder; +import io.quarkus.builder.item.MultiBuildItem; + +/** + * This build item should be considered private and should not be used outside of core Quarkus. + * It can be changed without notice. + */ +public final class EventConsumerInvokerCustomizerBuildItem extends MultiBuildItem { + private final BiConsumer invokerCustomizer; + + public EventConsumerInvokerCustomizerBuildItem(BiConsumer invokerCustomizer) { + this.invokerCustomizer = invokerCustomizer; + } + + public BiConsumer getInvokerCustomizer() { + return invokerCustomizer; + } +} diff --git a/extensions/vertx/deployment/pom.xml b/extensions/vertx/deployment/pom.xml index fd96dfafdae571..3caa22b6d250b1 100644 --- a/extensions/vertx/deployment/pom.xml +++ b/extensions/vertx/deployment/pom.xml @@ -37,6 +37,10 @@ io.quarkus quarkus-jackson-spi + + io.quarkus + quarkus-vertx-deployment-spi + io.quarkus quarkus-junit5-internal diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java index 28592e9d9839fe..1c3d7fb2e99159 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java @@ -35,6 +35,7 @@ import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.arc.processor.InvokerBuilder; import io.quarkus.arc.processor.InvokerInfo; +import io.quarkus.arc.processor.KotlinUtils; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; @@ -59,6 +60,7 @@ import io.quarkus.gizmo.ClassOutput; import io.quarkus.vertx.ConsumeEvent; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; +import io.quarkus.vertx.deployment.spi.EventConsumerInvokerCustomizerBuildItem; import io.quarkus.vertx.runtime.EventConsumerInfo; import io.quarkus.vertx.runtime.VertxEventBusConsumerRecorder; import io.quarkus.vertx.runtime.VertxProducer; @@ -137,6 +139,7 @@ public UnremovableBeanBuildItem unremovableBeans() { void collectEventConsumers( BeanRegistrationPhaseBuildItem beanRegistrationPhase, InvokerFactoryBuildItem invokerFactory, + List invokerCustomizers, BuildProducer messageConsumerBusinessMethods, BuildProducer errors) { // We need to collect all business methods annotated with @ConsumeEvent first @@ -149,8 +152,13 @@ void collectEventConsumers( AnnotationInstance consumeEvent = annotationStore.getAnnotation(method, CONSUME_EVENT); if (consumeEvent != null) { // Validate method params and return type + int parametersCount = method.parametersCount(); + if (KotlinUtils.isKotlinSuspendMethod(method)) { + parametersCount--; + } + List params = method.parameterTypes(); - if (params.size() == 2) { + if (parametersCount == 2) { if (!isMessageHeaders(params.get(0).name())) { // If there are two parameters, the first must be message headers. throw new IllegalStateException(String.format( @@ -161,12 +169,13 @@ void collectEventConsumers( "An event consumer business method with two parameters must not accept io.vertx.core.eventbus.Message or io.vertx.mutiny.core.eventbus.Message: %s [method: %s, bean:%s]", params, method, bean)); } - } else if (params.size() != 1) { + } else if (parametersCount != 1) { throw new IllegalStateException(String.format( "An event consumer business method must accept exactly one parameter: %s [method: %s, bean:%s]", params, method, bean)); } - if (method.returnType().kind() != Kind.VOID && VertxConstants.isMessage(params.get(0).name())) { + if (method.returnType().kind() != Kind.VOID && VertxConstants.isMessage(params.get(0).name()) + && !KotlinUtils.isKotlinSuspendMethod(method)) { throw new IllegalStateException(String.format( "An event consumer business method that accepts io.vertx.core.eventbus.Message or io.vertx.mutiny.core.eventbus.Message must return void [method: %s, bean:%s]", method, bean)); @@ -181,16 +190,16 @@ void collectEventConsumers( InvokerBuilder builder = invokerFactory.createInvoker(bean, method) .withInstanceLookup(); - if (method.parametersCount() == 1 && method.parameterType(0).name().equals(MESSAGE)) { + if (parametersCount == 1 && method.parameterType(0).name().equals(MESSAGE)) { // io.vertx.core.eventbus.Message // no transformation required - } else if (method.parametersCount() == 1 && method.parameterType(0).name().equals(MUTINY_MESSAGE)) { + } else if (parametersCount == 1 && method.parameterType(0).name().equals(MUTINY_MESSAGE)) { // io.vertx.mutiny.core.eventbus.Message builder.withArgumentTransformer(0, io.vertx.mutiny.core.eventbus.Message.class, "newInstance"); - } else if (method.parametersCount() == 1) { + } else if (parametersCount == 1) { // parameter is payload builder.withArgumentTransformer(0, io.vertx.core.eventbus.Message.class, "body"); - } else if (method.parametersCount() == 2 && method.parameterType(0).name().equals(MUTINY_MESSAGE_HEADERS)) { + } else if (parametersCount == 2 && method.parameterType(0).name().equals(MUTINY_MESSAGE_HEADERS)) { // if the method expects Mutiny MultiMap, wrap the Vert.x MultiMap builder.withArgumentTransformer(0, io.vertx.mutiny.core.MultiMap.class, "newInstance"); } @@ -199,11 +208,16 @@ void collectEventConsumers( builder.withReturnValueTransformer(Uni.class, "subscribeAsCompletionStage"); } + // the rest of Kotlin suspend function support is in the `vertx-kotlin` extension + for (EventConsumerInvokerCustomizerBuildItem invokerCustomizer : invokerCustomizers) { + invokerCustomizer.getInvokerCustomizer().accept(method, builder); + } + InvokerInfo invoker = builder.build(); messageConsumerBusinessMethods.produce(new EventConsumerBusinessMethodItem(bean, consumeEvent, method.hasAnnotation(Blocking.class), method.hasAnnotation(RunOnVirtualThread.class), - params.size() == 2, invoker)); + parametersCount == 2, invoker)); LOGGER.debugf("Found event consumer business method %s declared on %s", method, bean); } } diff --git a/extensions/vertx/kotlin/deployment/pom.xml b/extensions/vertx/kotlin/deployment/pom.xml new file mode 100644 index 00000000000000..45cb0e8d86ae92 --- /dev/null +++ b/extensions/vertx/kotlin/deployment/pom.xml @@ -0,0 +1,52 @@ + + + + quarkus-vertx-kotlin-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-vertx-kotlin-deployment + Quarkus - Vert.x - Kotlin - Deployment + + + + io.quarkus + quarkus-vertx-kotlin + + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-vertx-deployment-spi + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + de.thetaphi + forbiddenapis + + + + + diff --git a/extensions/vertx/kotlin/deployment/src/main/java/io/quarkus/vertx/kotlin/deployment/VertxKotlinProcessor.java b/extensions/vertx/kotlin/deployment/src/main/java/io/quarkus/vertx/kotlin/deployment/VertxKotlinProcessor.java new file mode 100644 index 00000000000000..8c7dbbb22c778a --- /dev/null +++ b/extensions/vertx/kotlin/deployment/src/main/java/io/quarkus/vertx/kotlin/deployment/VertxKotlinProcessor.java @@ -0,0 +1,48 @@ +package io.quarkus.vertx.kotlin.deployment; + +import java.util.function.BiConsumer; + +import org.jboss.jandex.MethodInfo; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.processor.InvokerBuilder; +import io.quarkus.arc.processor.KotlinUtils; +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.vertx.deployment.spi.EventConsumerInvokerCustomizerBuildItem; +import io.quarkus.vertx.kotlin.runtime.ApplicationCoroutineScope; +import io.quarkus.vertx.kotlin.runtime.CoroutineInvoker; + +public class VertxKotlinProcessor { + private static final String KOTLIN_COROUTINE_SCOPE = "kotlinx.coroutines.CoroutineScope"; + + @BuildStep + void produceCoroutineScope(BuildProducer additionalBean) { + if (!QuarkusClassLoader.isClassPresentAtRuntime(KOTLIN_COROUTINE_SCOPE)) { + return; + } + + additionalBean.produce(AdditionalBeanBuildItem.builder() + .addBeanClass(ApplicationCoroutineScope.class) + .setUnremovable() + .build()); + } + + @BuildStep + void produceInvokerCustomizerForSuspendConsumeEventMethods( + BuildProducer customizers) { + if (!QuarkusClassLoader.isClassPresentAtRuntime(KOTLIN_COROUTINE_SCOPE)) { + return; + } + + customizers.produce(new EventConsumerInvokerCustomizerBuildItem(new BiConsumer() { + @Override + public void accept(MethodInfo method, InvokerBuilder invokerBuilder) { + if (KotlinUtils.isKotlinSuspendMethod(method)) { + invokerBuilder.withInvocationWrapper(CoroutineInvoker.class, "inNewCoroutine"); + } + } + })); + } +} diff --git a/extensions/vertx/kotlin/pom.xml b/extensions/vertx/kotlin/pom.xml new file mode 100644 index 00000000000000..92dfa833c09bc7 --- /dev/null +++ b/extensions/vertx/kotlin/pom.xml @@ -0,0 +1,19 @@ + + + + quarkus-vertx-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-vertx-kotlin-parent + Quarkus - Vert.x - Kotlin + pom + + deployment + runtime + + diff --git a/extensions/vertx/kotlin/runtime/pom.xml b/extensions/vertx/kotlin/runtime/pom.xml new file mode 100644 index 00000000000000..ec1e2e7f975b36 --- /dev/null +++ b/extensions/vertx/kotlin/runtime/pom.xml @@ -0,0 +1,121 @@ + + + + quarkus-vertx-kotlin-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-vertx-kotlin + Quarkus - Vert.x - Kotlin - Runtime + + + + io.quarkus + quarkus-arc + + + io.vertx + vertx-core + + + org.jetbrains.kotlin + kotlin-stdlib + true + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + true + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + + io.quarkus + quarkus-extension-maven-plugin + + + io.quarkus:quarkus-vertx + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + + compile + + + + test-compile + + test-compile + + + + + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + false + + + + + diff --git a/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/ApplicationCoroutineScope.kt b/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/ApplicationCoroutineScope.kt new file mode 100644 index 00000000000000..7a9719fdbeb86b --- /dev/null +++ b/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/ApplicationCoroutineScope.kt @@ -0,0 +1,18 @@ +package io.quarkus.vertx.kotlin.runtime + +import jakarta.annotation.PreDestroy +import jakarta.inject.Singleton +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel + +@Singleton +class ApplicationCoroutineScope : CoroutineScope, AutoCloseable { + override val coroutineContext: CoroutineContext = SupervisorJob() + + @PreDestroy + override fun close() { + coroutineContext.cancel() + } +} diff --git a/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/CoroutineInvoker.kt b/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/CoroutineInvoker.kt new file mode 100644 index 00000000000000..48e5a26f084f0f --- /dev/null +++ b/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/CoroutineInvoker.kt @@ -0,0 +1,36 @@ +package io.quarkus.vertx.kotlin.runtime + +import io.quarkus.arc.Arc +import io.vertx.core.Vertx +import jakarta.enterprise.invoke.Invoker +import java.util.concurrent.CompletionStage +import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture + +object CoroutineInvoker { + @JvmStatic + fun inNewCoroutine( + instance: T, + arguments: Array, + invoker: Invoker + ): CompletionStage { + val coroutineScope = Arc.container().instance(ApplicationCoroutineScope::class.java).get() + val dispatcher: CoroutineDispatcher = + Vertx.currentContext()?.let(::VertxDispatcher) + ?: throw IllegalStateException("No Vertx context found") + + return coroutineScope + .async(context = dispatcher) { + suspendCoroutine { continuation -> + val newArguments = arrayOfNulls(arguments.size + 1) + arguments.copyInto(newArguments) + newArguments[arguments.size] = continuation + invoker.invoke(instance, newArguments) + } + null + } + .asCompletableFuture() + } +} diff --git a/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/VertxDispatcher.kt b/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/VertxDispatcher.kt new file mode 100644 index 00000000000000..67cee1bfab8b89 --- /dev/null +++ b/extensions/vertx/kotlin/runtime/src/main/kotlin/io/quarkus/vertx/kotlin/runtime/VertxDispatcher.kt @@ -0,0 +1,24 @@ +package io.quarkus.vertx.kotlin.runtime + +import io.quarkus.arc.Arc +import io.vertx.core.Context +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineDispatcher + +class VertxDispatcher(private val vertxContext: Context) : CoroutineDispatcher() { + override fun dispatch(context: CoroutineContext, block: Runnable) { + val requestContext = Arc.container().requestContext() + vertxContext.runOnContext { + if (requestContext.isActive) { + block.run() + } else { + try { + requestContext.activate() + block.run() + } finally { + requestContext.terminate() + } + } + } + } +} diff --git a/extensions/vertx/kotlin/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/vertx/kotlin/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000000000..42269a5f6e8534 --- /dev/null +++ b/extensions/vertx/kotlin/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,5 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "Vert.x Kotlin integration" +metadata: + unlisted: true diff --git a/extensions/vertx/pom.xml b/extensions/vertx/pom.xml index e58674406920e1..6c19b787b8145d 100644 --- a/extensions/vertx/pom.xml +++ b/extensions/vertx/pom.xml @@ -15,6 +15,8 @@ pom deployment + deployment-spi + kotlin latebound-mdc-provider runtime diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 5a44f2591b35bc..d3397ea0df1eb6 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -230,6 +230,7 @@ vertx-web vertx-web-jackson vertx + vertx-kotlin websockets spring-di spring-web diff --git a/integration-tests/vertx-kotlin/pom.xml b/integration-tests/vertx-kotlin/pom.xml new file mode 100644 index 00000000000000..14a08e162a825d --- /dev/null +++ b/integration-tests/vertx-kotlin/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-integration-test-vertx-kotlin + Quarkus - Integration Tests - Vert.x Kotlin + + + + io.quarkus + quarkus-kotlin + + + io.quarkus + quarkus-vertx + + + io.quarkus + quarkus-junit5 + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-kotlin-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-vertx-deployment + ${project.version} + pom + test + + + * + * + + + + + + + src/main/kotlin + src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + diff --git a/integration-tests/vertx-kotlin/src/main/kotlin/io/quarkus/it/vertx/kotlin/MessageConsumers.kt b/integration-tests/vertx-kotlin/src/main/kotlin/io/quarkus/it/vertx/kotlin/MessageConsumers.kt new file mode 100644 index 00000000000000..d6ec06eedc5e4e --- /dev/null +++ b/integration-tests/vertx-kotlin/src/main/kotlin/io/quarkus/it/vertx/kotlin/MessageConsumers.kt @@ -0,0 +1,49 @@ +package io.quarkus.it.vertx.kotlin + +import io.quarkus.vertx.ConsumeEvent +import jakarta.inject.Singleton +import java.util.concurrent.CountDownLatch + +@Singleton +class MessageConsumers { + companion object { + @Volatile var latch: CountDownLatch = CountDownLatch(1) + + var message: String? = null + + fun reset() { + latch = CountDownLatch(1) + message = null + } + } + + @ConsumeEvent("message") + fun message(msg: io.vertx.core.eventbus.Message) { + message = msg.body() + latch.countDown() + } + + @ConsumeEvent("mutiny-message") + fun mutinyMessage(msg: io.vertx.mutiny.core.eventbus.Message) { + message = msg.body() + latch.countDown() + } + + @ConsumeEvent("payload") + fun payload(msg: String) { + message = msg + latch.countDown() + } + + @ConsumeEvent("headers-payload") + fun headersPayload(headers: io.vertx.core.MultiMap, msg: String) { + message = "${headers["header"]} - $msg" + latch.countDown() + } + + @ConsumeEvent("mutiny-headers-payload") + fun mutinyHeadersPayload(headers: io.vertx.mutiny.core.MultiMap, msg: String) { + message = "${headers["header"]} - $msg" + latch.countDown() + } +} diff --git a/integration-tests/vertx-kotlin/src/main/kotlin/io/quarkus/it/vertx/kotlin/SuspendMessageConsumers.kt b/integration-tests/vertx-kotlin/src/main/kotlin/io/quarkus/it/vertx/kotlin/SuspendMessageConsumers.kt new file mode 100644 index 00000000000000..58e5b76788e968 --- /dev/null +++ b/integration-tests/vertx-kotlin/src/main/kotlin/io/quarkus/it/vertx/kotlin/SuspendMessageConsumers.kt @@ -0,0 +1,55 @@ +package io.quarkus.it.vertx.kotlin + +import io.quarkus.vertx.ConsumeEvent +import jakarta.inject.Singleton +import java.util.concurrent.CountDownLatch +import kotlinx.coroutines.delay + +@Singleton +class SuspendMessageConsumers { + companion object { + @Volatile var latch: CountDownLatch = CountDownLatch(1) + + var message: String? = null + + fun reset() { + latch = CountDownLatch(1) + message = null + } + } + + @ConsumeEvent("suspend-message") + suspend fun message(msg: io.vertx.core.eventbus.Message) { + delay(100) + message = msg.body() + latch.countDown() + } + + @ConsumeEvent("suspend-mutiny-message") + suspend fun mutinyMessage(msg: io.vertx.mutiny.core.eventbus.Message) { + delay(100) + message = msg.body() + latch.countDown() + } + + @ConsumeEvent("suspend-payload") + suspend fun payload(msg: String) { + delay(100) + message = msg + latch.countDown() + } + + @ConsumeEvent("suspend-headers-payload") + suspend fun headersPayload(headers: io.vertx.core.MultiMap, msg: String) { + delay(100) + message = "${headers["header"]} - $msg" + latch.countDown() + } + + @ConsumeEvent("suspend-mutiny-headers-payload") + suspend fun mutinyHeadersPayload(headers: io.vertx.mutiny.core.MultiMap, msg: String) { + delay(100) + message = "${headers["header"]} - $msg" + latch.countDown() + } +} diff --git a/integration-tests/vertx-kotlin/src/test/kotlin/io/quarkus/it/vertx/kotlin/KotlinConsumeEventTest.kt b/integration-tests/vertx-kotlin/src/test/kotlin/io/quarkus/it/vertx/kotlin/KotlinConsumeEventTest.kt new file mode 100644 index 00000000000000..5b5034bd4f918e --- /dev/null +++ b/integration-tests/vertx-kotlin/src/test/kotlin/io/quarkus/it/vertx/kotlin/KotlinConsumeEventTest.kt @@ -0,0 +1,62 @@ +package io.quarkus.it.vertx.kotlin + +import io.quarkus.test.junit.QuarkusTest +import io.vertx.core.eventbus.DeliveryOptions +import io.vertx.core.eventbus.EventBus +import jakarta.inject.Inject +import java.util.concurrent.TimeUnit +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +@QuarkusTest +class KotlinConsumeEventTest { + @Inject lateinit var bus: EventBus + + @BeforeEach + fun setup() { + MessageConsumers.reset() + } + + @Test + fun message() { + bus.send("message", "message") + + MessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("message", MessageConsumers.message) + } + + @Test + fun mutinyMessage() { + bus.send("mutiny-message", "mutiny message") + + MessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("mutiny message", MessageConsumers.message) + } + + @Test + fun payload() { + bus.send("payload", "just payload") + + MessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("just payload", MessageConsumers.message) + } + + @Test + fun headersPayload() { + val options = DeliveryOptions().addHeader("header", "test header") + bus.send("headers-payload", "payload", options) + + MessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("test header - payload", MessageConsumers.message) + } + + @Test + fun mutinyHeadersPayload() { + val options = DeliveryOptions().addHeader("header", "test mutiny header") + bus.send("mutiny-headers-payload", "payload", options) + + MessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("test mutiny header - payload", MessageConsumers.message) + } +} diff --git a/integration-tests/vertx-kotlin/src/test/kotlin/io/quarkus/it/vertx/kotlin/KotlinSuspendConsumeEventTest.kt b/integration-tests/vertx-kotlin/src/test/kotlin/io/quarkus/it/vertx/kotlin/KotlinSuspendConsumeEventTest.kt new file mode 100644 index 00000000000000..975c80e4284264 --- /dev/null +++ b/integration-tests/vertx-kotlin/src/test/kotlin/io/quarkus/it/vertx/kotlin/KotlinSuspendConsumeEventTest.kt @@ -0,0 +1,62 @@ +package io.quarkus.it.vertx.kotlin + +import io.quarkus.test.junit.QuarkusTest +import io.vertx.core.eventbus.DeliveryOptions +import io.vertx.core.eventbus.EventBus +import jakarta.inject.Inject +import java.util.concurrent.TimeUnit +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +@QuarkusTest +class KotlinSuspendConsumeEventTest { + @Inject lateinit var bus: EventBus + + @BeforeEach + fun setup() { + SuspendMessageConsumers.reset() + } + + @Test + fun message() { + bus.send("suspend-message", "message") + + SuspendMessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("message", SuspendMessageConsumers.message) + } + + @Test + fun mutinyMessage() { + bus.send("suspend-mutiny-message", "mutiny message") + + SuspendMessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("mutiny message", SuspendMessageConsumers.message) + } + + @Test + fun payload() { + bus.send("suspend-payload", "just payload") + + SuspendMessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("just payload", SuspendMessageConsumers.message) + } + + @Test + fun headersPayload() { + val options = DeliveryOptions().addHeader("header", "test header") + bus.send("suspend-headers-payload", "payload", options) + + SuspendMessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("test header - payload", SuspendMessageConsumers.message) + } + + @Test + fun mutinyHeadersPayload() { + val options = DeliveryOptions().addHeader("header", "test mutiny header") + bus.send("suspend-mutiny-headers-payload", "payload", options) + + SuspendMessageConsumers.latch.await(2, TimeUnit.SECONDS) + assertEquals("test mutiny header - payload", SuspendMessageConsumers.message) + } +}