├── src └── main │ └── docs │ └── guide │ ├── schemas.adoc │ ├── repository.adoc │ ├── introduction.adoc │ ├── protobuf.adoc │ ├── toc.yml │ ├── tls.adoc │ ├── releaseHistory.adoc │ ├── reader.adoc │ ├── quickStart.adoc │ ├── authentication.adoc │ └── producer.adoc ├── pulsar-bom └── build.gradle ├── .github ├── workflows │ ├── .rsync-filter │ ├── publish-snapshot.yml │ ├── central-sync.yml │ ├── graalvm-latest.yml │ ├── graalvm-dev.yml │ └── gradle.yml ├── ISSUE_TEMPLATE │ ├── other.yaml │ ├── new_feature.yaml │ ├── config.yml │ └── bug_report.yaml ├── renovate.json ├── release.yml └── instructions │ └── docs.instructions.md ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── license.gradle └── libs.versions.toml ├── pulsar ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── native-image │ │ │ └── io.netty │ │ │ └── transport │ │ │ └── native-image.properties │ │ └── java │ │ └── io │ │ └── micronaut │ │ └── pulsar │ │ ├── config │ │ ├── package-info.java │ │ ├── AbstractPulsarConfiguration.java │ │ └── PulsarClientConfiguration.java │ │ ├── package-info.java │ │ ├── processor │ │ ├── package-info.java │ │ ├── MessageListenerResolver.java │ │ ├── ListenerKotlinHelper.java │ │ ├── DefaultTopicResolver.java │ │ └── DefaultListener.java │ │ ├── schemas │ │ ├── json │ │ │ ├── package-info.java │ │ │ ├── JsonSchemaResolver.java │ │ │ ├── JsonWriter.java │ │ │ ├── JsonSchema.java │ │ │ └── JsonReader.java │ │ ├── package-info.java │ │ ├── protobuf │ │ │ ├── ProtobufWriter.java │ │ │ ├── ProtobufReader.java │ │ │ ├── ProtobufSchemaResolver.java │ │ │ └── ProtobufSchema.java │ │ ├── avro │ │ │ └── AvroSchemaResolver.java │ │ └── SchemaResolver.java │ │ ├── events │ │ ├── package-info.java │ │ ├── PulsarFailureEvent.java │ │ ├── ConsumerSubscribedEvent.java │ │ ├── ProducerSubscriptionFailedEvent.java │ │ └── ConsumerSubscriptionFailedEvent.java │ │ ├── annotation │ │ ├── package-info.java │ │ ├── MessageKey.java │ │ ├── MessageProperties.java │ │ ├── PulsarServiceUrlProvider.java │ │ ├── PulsarProducerClient.java │ │ ├── PulsarReaderClient.java │ │ ├── PulsarSubscription.java │ │ ├── PulsarReader.java │ │ └── PulsarProducer.java │ │ ├── intercept │ │ └── package-info.java │ │ ├── PulsarReaderRegistry.java │ │ ├── PulsarConsumerRegistry.java │ │ ├── PulsarProducerRegistry.java │ │ ├── MessageSchema.java │ │ └── PulsarClientFactory.java └── build.gradle ├── test-suite ├── test-pulsar-module │ ├── src │ │ └── test │ │ │ ├── protobuf │ │ │ └── message.proto │ │ │ ├── java │ │ │ └── io │ │ │ │ └── micronaut │ │ │ │ └── pulsar │ │ │ │ └── JsonJavaClass.java │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── groovy │ │ │ └── io │ │ │ └── micronaut │ │ │ └── pulsar │ │ │ ├── config │ │ │ └── TopicNameValidatorTest.groovy │ │ │ ├── PulsarAwareTest.groovy │ │ │ ├── PulsarConfigurationTest.groovy │ │ │ ├── PulsarProducersSpec.groovy │ │ │ └── PulsarSchemaSpec.groovy │ └── build.gradle ├── test-pulsar-multitenant-module │ ├── src │ │ └── test │ │ │ ├── resources │ │ │ └── logback-test.xml │ │ │ └── groovy │ │ │ └── io │ │ │ └── micronaut │ │ │ └── pulsar │ │ │ ├── dynamic │ │ │ ├── DynamicReader.groovy │ │ │ ├── ProducerDynamicTenantTopicTester.groovy │ │ │ ├── FakeClient.groovy │ │ │ ├── ConsumerDynamicTenantTopicTester.groovy │ │ │ └── FakeController.groovy │ │ │ ├── PulsarConfigurationSpec.groovy │ │ │ └── FixedTenantTopicResolverSpec.groovy │ └── build.gradle └── test-pulsar-shared-module │ ├── build.gradle │ └── src │ └── test │ └── resources │ ├── broker.key-pk8.pem │ ├── broker.cert.pem │ ├── ca.cert.pem │ └── client.conf ├── pulsar-multitenant ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── micronaut │ └── pulsar │ ├── processor │ ├── package-info.java │ ├── MultiTenantTopicResolver.java │ ├── TenantNameResolver.java │ └── ToStringTenantNameResolver.java │ ├── events │ ├── package-info.java │ └── PulsarTenantDiscoveredEvent.java │ └── intercept │ └── package-info.java ├── doc-examples ├── example-java │ ├── build.gradle │ └── src │ │ └── test │ │ ├── resources │ │ └── logback.xml │ │ └── java │ │ └── example │ │ ├── ReaderExample.java │ │ ├── Producer.java │ │ └── ConsumerProducer.java ├── example-groovy │ ├── src │ │ └── test │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── groovy │ │ │ └── example │ │ │ ├── ReaderExample.groovy │ │ │ ├── Producer.groovy │ │ │ └── ConsumerProducer.groovy │ └── build.gradle └── example-kotlin │ ├── src │ └── test │ │ ├── resources │ │ └── logback.xml │ │ └── kotlin │ │ └── example │ │ ├── ReaderExample.kt │ │ ├── Producer.kt │ │ └── ConsumerProducer.kt │ └── build.gradle ├── gradle.properties ├── config ├── checkstyle │ ├── suppressions.xml │ └── custom-suppressions.xml ├── HEADER └── spotless.license.java ├── .gitattributes ├── .editorconfig ├── .clineignore ├── SECURITY.md ├── .gitignore ├── settings.gradle ├── ISSUE_TEMPLATE.md ├── README.md ├── .clinerules └── docs.md ├── CONTRIBUTING.md └── gradlew.bat /src/main/docs/guide/schemas.adoc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pulsar-bom/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.micronaut.build.internal.bom" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/.rsync-filter: -------------------------------------------------------------------------------- 1 | - files-sync.yml 2 | - test-files-sync.yml 3 | - update-gradle-wrapper.yml 4 | - .rsync-filter -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micronaut-projects/micronaut-pulsar/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/docs/guide/repository.adoc: -------------------------------------------------------------------------------- 1 | You can find the source code of this project in this repository: 2 | 3 | https://github.com/{githubSlug}[https://github.com/{githubSlug}] -------------------------------------------------------------------------------- /pulsar/src/main/resources/META-INF/native-image/io.netty/transport/native-image.properties: -------------------------------------------------------------------------------- 1 | Args = --initialize-at-build-time=org.apache.pulsar.client.impl.auth.AuthenticationDisabled -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yaml: -------------------------------------------------------------------------------- 1 | name: Other 2 | description: Something different 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Issue description 7 | validations: 8 | required: true 9 | 10 | -------------------------------------------------------------------------------- /src/main/docs/guide/introduction.adoc: -------------------------------------------------------------------------------- 1 | This project includes integration between Micronaut framework and Apache Pulsar. It tries to simplify configuration 2 | and replace building consumers, producers, and readers with annotation based approach. 3 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/src/test/protobuf/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | option java_package = "io.micronaut.pulsar"; 3 | option java_outer_classname = "ProtoMessages"; 4 | 5 | message ProtoMessage { 6 | string message = 1; 7 | int32 number = 2; 8 | } -------------------------------------------------------------------------------- /src/main/docs/guide/protobuf.adoc: -------------------------------------------------------------------------------- 1 | == Protocol buffers support 2 | In order to allow protocol buffer *native* messaging support you need to include support for protocol buffers 3 | google from micronaut dependencies 4 | 5 | dependency::micronaut-protobuff-support[groupId="io.micronaut.grpc"] 6 | -------------------------------------------------------------------------------- /pulsar-multitenant/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.pulsar-multitenant-module' 3 | } 4 | 5 | dependencies { 6 | implementation projects.micronautPulsar 7 | implementation mnMultitenancy.micronaut.multitenancy 8 | compileOnly mnReactor.micronaut.reactor 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /doc-examples/example-java/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.pulsar-example") 3 | } 4 | 5 | micronaut { 6 | version libs.versions.micronaut.platform.get() 7 | } 8 | dependencies { 9 | testImplementation projects.micronautPulsar 10 | testCompileOnly mn.micronaut.inject.java 11 | testImplementation mn.micronaut.inject 12 | } 13 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | projectVersion=3.0.0-SNAPSHOT 2 | projectGroup=io.micronaut.pulsar 3 | title=Micronaut Pulsar 4 | projectDesc=Integration between Micronaut and Apache Pulsar 5 | projectUrl=https://micronaut.io 6 | githubSlug=micronaut-projects/micronaut-pulsar 7 | developers=Haris Secic 8 | jdkapi=https://docs.oracle.com/en/java/javase/17/docs/api 9 | apimicronaut=https://docs.micronaut.io/latest/api/io/micronaut/ 10 | org.gradle.caching=true 11 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/src/test/java/io/micronaut/pulsar/JsonJavaClass.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.pulsar; 2 | 3 | import io.micronaut.context.annotation.Requires; 4 | 5 | @Requires(property = "spec.name", value = "PulsarConfigurationTest") 6 | public class JsonJavaClass { 7 | private String id; 8 | 9 | public String getId() { 10 | return id; 11 | } 12 | 13 | public void setId(String id) { 14 | this.id = id; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /doc-examples/example-java/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc-examples/example-groovy/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc-examples/example-kotlin/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/docs/guide/toc.yml: -------------------------------------------------------------------------------- 1 | introduction: 2 | title: Introduction 3 | releaseHistory: Release History 4 | quickStart: 5 | title: Quick Start 6 | consumer: 7 | title: Pulsar consumers 8 | reader: 9 | title: Pulsar readers 10 | producer: 11 | title: Pulsar producers 12 | authentication: 13 | title: Authentication 14 | tls: 15 | title: Transport encryption 16 | protobuf: 17 | title: Protocol Buffers 18 | multitenancy: 19 | title: Multitenancy 20 | repository: Repository 21 | -------------------------------------------------------------------------------- /config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_feature.yaml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Create a new feature request 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Please describe the feature you want for Micronaut to implement, before that check if there is already an existing issue to add it. 8 | - type: textarea 9 | attributes: 10 | label: Feature description 11 | placeholder: Tell us what feature you would like for Micronaut to have and what problem is it going to solve 12 | validations: 13 | required: true 14 | 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | *.java text eol=lf 5 | *.groovy text eol=lf 6 | *.html text eol=lf 7 | *.kt text eol=lf 8 | *.kts text eol=lf 9 | *.md text diff=markdown eol=lf 10 | *.py text diff=python executable 11 | *.pl text diff=perl executable 12 | *.pm text diff=perl 13 | *.css text diff=css eol=lf 14 | *.js text eol=lf 15 | *.sql text eol=lf 16 | *.q text eol=lf 17 | 18 | *.sh text eol=lf 19 | gradlew text eol=lf 20 | 21 | *.bat text eol=crlf 22 | *.cmd text eol=crlf 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | 9 | [{*.sh,gradlew}] 10 | end_of_line = lf 11 | 12 | [{*.bat,*.cmd}] 13 | end_of_line = crlf 14 | 15 | [{*.mustache,*.ftl}] 16 | insert_final_newline = false 17 | 18 | [*.java] 19 | indent_size = 4 20 | tab_width = 4 21 | max_line_length = 100 22 | # Import order can be configured with ij_java_imports_layout=... 23 | # See documentation https://youtrack.jetbrains.com/issue/IDEA-170643#focus=streamItem-27-3708697.0-0 24 | 25 | [*.xml] 26 | indent_size = 4 27 | -------------------------------------------------------------------------------- /config/HEADER: -------------------------------------------------------------------------------- 1 | Copyright ${year} original authors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /config/checkstyle/custom-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.test-pulsar-module' 3 | } 4 | 5 | sourceSets { 6 | test { 7 | resources { 8 | srcDir '../test-pulsar-shared-module/src/test/resources' 9 | include 'broker.cert.pem', 'broker.key-pk8.pem', 'ca.cert.pem', 'standalone.conf', 'client.conf' 10 | } 11 | } 12 | } 13 | 14 | //TODO remove once Micronaut Test ships Spock version compatible with Groovy 5 15 | configurations.all { 16 | resolutionStrategy { 17 | force("org.spockframework:spock-core:2.4-groovy-5.0") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Micronaut Core Discussions 3 | url: https://github.com/micronaut-projects/micronaut-core/discussions 4 | about: Ask questions about Micronaut on Github 5 | - name: Micronaut Data Discussions 6 | url: https://github.com/micronaut-projects/micronaut-data/discussions 7 | about: Ask Micronaut Data related questions on Github 8 | - name: Stack Overflow 9 | url: https://stackoverflow.com/tags/micronaut 10 | about: Ask questions on Stack Overflow 11 | - name: Chat 12 | url: https://gitter.im/micronautfw/ 13 | about: Chat with us on Gitter. -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 7 | 8 | %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 7 | 8 | %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /doc-examples/example-kotlin/src/test/kotlin/example/ReaderExample.kt: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import io.micronaut.pulsar.annotation.PulsarReader 4 | import jakarta.inject.Singleton 5 | import kotlinx.coroutines.future.await 6 | import org.apache.pulsar.client.api.Message 7 | import org.apache.pulsar.client.api.Reader 8 | 9 | @Singleton 10 | class ReaderExample { 11 | @PulsarReader(value = "persistent://public/default/messages", readerName = "simple-k-reader") // <1> 12 | private lateinit var reader: Reader // <2> 13 | 14 | suspend fun readNext(): Message { // <3> 15 | return reader.readNextAsync().await() // <4> 16 | } 17 | } -------------------------------------------------------------------------------- /.clineignore: -------------------------------------------------------------------------------- 1 | # .clineignore - Cline AI ignore file for Micronaut projects 2 | # This file tells Cline which files/directories to ignore for code intelligence and automation 3 | 4 | # === Build outputs === 5 | build/ 6 | */build/ 7 | !build/docs/ 8 | !build/docs/** 9 | !build/generated/ 10 | !build/generated/** 11 | 12 | # === Dependency/Cache directories === 13 | .gradle/ 14 | */.gradle/ 15 | 16 | # === IDE/Editor/OS Metadata === 17 | .vscode/ 18 | .idea/ 19 | .DS_Store 20 | *.swp 21 | *.swo 22 | *.bak 23 | *.tmp 24 | *.orig 25 | 26 | # === Tool-specific/Config artifacts === 27 | *.log 28 | 29 | # === Gradle Wrappers/Binaries === 30 | gradlew 31 | gradlew.bat 32 | -------------------------------------------------------------------------------- /gradle/license.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.hierynomus.license' 2 | 3 | license { 4 | header = rootProject.file('config/HEADER') 5 | strictCheck = true 6 | ignoreFailures = true 7 | mapping { 8 | kt = 'SLASHSTAR_STYLE' 9 | java = 'SLASHSTAR_STYLE' 10 | groovy = 'SLASHSTAR_STYLE' 11 | } 12 | ext.year = '2017-2020' 13 | 14 | exclude "**/transaction/**" 15 | exclude '**/*.txt' 16 | exclude '**/*.html' 17 | exclude '**/*.xml' 18 | exclude '**/*.json' 19 | exclude '**/build-info.properties' 20 | exclude '**/git.properties' 21 | exclude '**/othergit.properties' 22 | } -------------------------------------------------------------------------------- /config/spotless.license.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-$YEAR original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /doc-examples/example-groovy/src/test/groovy/example/ReaderExample.groovy: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import io.micronaut.pulsar.annotation.PulsarReader; 4 | import jakarta.inject.Singleton; 5 | import org.apache.pulsar.client.api.Message; 6 | import org.apache.pulsar.client.api.Reader; 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | 10 | 11 | @Singleton 12 | class ReaderExample { 13 | 14 | @PulsarReader(value = "persistent://public/default/messages", readerName = "simple-g-reader") // <1> 15 | private Reader reader // <2> 16 | 17 | CompletableFuture> readNext() { // <3> 18 | return reader.readNextAsync() // <4> 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /doc-examples/example-kotlin/src/test/kotlin/example/Producer.kt: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import io.micronaut.pulsar.annotation.PulsarProducer 4 | import io.micronaut.pulsar.annotation.PulsarProducerClient 5 | import org.apache.pulsar.client.api.MessageId 6 | 7 | @PulsarProducerClient // <1> 8 | interface Producer { 9 | @PulsarProducer(topic = "persistent://public/default/messages-kotlin-docs", producerName = "kotlin-test-producer") // <2> 10 | suspend fun send(message: String): MessageId // <3> 11 | @PulsarProducer(topic = "persistent://public/default/messages-kotlin-docs", producerName = "b-kotlin-test-producer") 12 | fun sendBlocking(message: String) // <4> 13 | } 14 | 15 | -------------------------------------------------------------------------------- /doc-examples/example-java/src/test/java/example/ReaderExample.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import io.micronaut.pulsar.annotation.PulsarReader; 4 | import jakarta.inject.Singleton; 5 | import org.apache.pulsar.client.api.Message; 6 | import org.apache.pulsar.client.api.Reader; 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | 10 | 11 | @Singleton 12 | public class ReaderExample { 13 | 14 | @PulsarReader(value = "persistent://public/default/messages", readerName = "simple-j-reader") // <1> 15 | private Reader reader; // <2> 16 | 17 | public CompletableFuture> readNext() { // <3> 18 | return reader.readNextAsync(); // <4> 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /doc-examples/example-groovy/src/test/groovy/example/Producer.groovy: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import io.micronaut.pulsar.annotation.PulsarProducer; 4 | import io.micronaut.pulsar.annotation.PulsarProducerClient; 5 | import org.apache.pulsar.client.api.MessageId; 6 | 7 | import java.util.concurrent.CompletableFuture; 8 | 9 | @PulsarProducerClient // <1> 10 | interface Producer { 11 | @PulsarProducer(topic = "persistent://public/default/messages-kotlin-docs", producerName = "kotlin-test-producer") // <2> 12 | CompletableFuture send(String message); // <3> 13 | 14 | @PulsarProducer(topic = "persistent://public/default/messages-kotlin-docs", producerName = "b-kotlin-test-producer") 15 | void sendBlocking(String message); // <4> 16 | } 17 | -------------------------------------------------------------------------------- /doc-examples/example-java/src/test/java/example/Producer.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import io.micronaut.pulsar.annotation.PulsarProducer; 4 | import io.micronaut.pulsar.annotation.PulsarProducerClient; 5 | import org.apache.pulsar.client.api.MessageId; 6 | 7 | import java.util.concurrent.CompletableFuture; 8 | 9 | @PulsarProducerClient // <1> 10 | public interface Producer { 11 | @PulsarProducer(topic = "persistent://public/default/messages-kotlin-docs", producerName = "kotlin-test-producer") // <2> 12 | CompletableFuture send(String message); // <3> 13 | 14 | @PulsarProducer(topic = "persistent://public/default/messages-kotlin-docs", producerName = "b-kotlin-test-producer") 15 | void sendBlocking(String message); // <4> 16 | } 17 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended" 4 | ], 5 | "addLabels": [ 6 | "type: dependency-upgrade" 7 | ], 8 | "schedule": [ 9 | "after 10pm" 10 | ], 11 | "prHourlyLimit": 1, 12 | "prConcurrentLimit": 20, 13 | "timezone": "Europe/Prague", 14 | "packageRules": [ 15 | { 16 | "dependencyDashboardApproval": true, 17 | "matchUpdateTypes": [ 18 | "patch" 19 | ], 20 | "matchCurrentVersion": "!/^0/", 21 | "automerge": true, 22 | "matchPackageNames": [ 23 | "/actions.*/" 24 | ] 25 | }, 26 | { 27 | "matchUpdateTypes": [ 28 | "patch" 29 | ], 30 | "matchCurrentVersion": "!/^0/", 31 | "automerge": true 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | We release patches for security vulnerabilities. Which versions are eligible 4 | receiving such patches depend on the CVSS v3.0 Rating: 5 | 6 | | CVSS v3.0 | Supported Versions | 7 | |-----------|-------------------------------------------| 8 | | 9.0-10.0 | Releases within the previous three months | 9 | | 4.0-8.9 | Most recent release | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Please responsibly disclose (suspected) security vulnerabilities to 14 | **[The Micronaut Foundation](foundation@micronaut.io)**. You will receive a response from 15 | us within 48 hours. If the issue is confirmed, we will release a patch as soon 16 | as possible depending on complexity but historically within a few days. 17 | -------------------------------------------------------------------------------- /doc-examples/example-groovy/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.pulsar-example") 3 | id "groovy" 4 | } 5 | micronaut { 6 | version libs.versions.micronaut.platform.get() 7 | } 8 | 9 | dependencies { 10 | testImplementation projects.micronautPulsar 11 | 12 | testCompileOnly mn.micronaut.inject.groovy 13 | testImplementation mn.micronaut.inject 14 | testImplementation mnTest.micronaut.test.spock 15 | } 16 | 17 | tasks.withType(GroovyCompile).configureEach { 18 | groovyOptions.forkOptions.jvmArgs.add('-Dgroovy.parameters=true') 19 | } 20 | 21 | //TODO remove once Micronaut Test ships Spock version compatible with Groovy 5 22 | configurations.all { 23 | resolutionStrategy { 24 | force("org.spockframework:spock-core:2.4-groovy-5.0") 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.test-pulsar-module' 3 | } 4 | 5 | dependencies { 6 | testImplementation projects.micronautPulsarMultitenant 7 | testImplementation mnMultitenancy.micronaut.multitenancy 8 | testImplementation mn.micronaut.http.client 9 | } 10 | 11 | sourceSets { 12 | test { 13 | resources { 14 | srcDir '../test-pulsar-shared-module/src/test/resources' 15 | include 'broker.cert.pem', 'broker.key-pk8.pem', 'ca.cert.pem', 'standalone.conf', 'client.conf' 16 | } 17 | } 18 | } 19 | 20 | //TODO remove once Micronaut Test ships Spock version compatible with Groovy 5 21 | configurations.all { 22 | resolutionStrategy { 23 | force("org.spockframework:spock-core:2.4-groovy-5.0") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /doc-examples/example-kotlin/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.pulsar-example") 3 | id("io.micronaut.build.internal.kotlin-kapt") 4 | } 5 | micronaut { 6 | version libs.versions.micronaut.platform.get() 7 | } 8 | dependencies { 9 | kaptTest enforcedPlatform(mn.micronaut.core.bom) 10 | kaptTest mn.micronaut.inject.java 11 | 12 | testImplementation projects.micronautPulsar 13 | testImplementation libs.kotest.runner 14 | testImplementation(mnTestResources.testcontainers.core) 15 | implementation mn.kotlinx.coroutines.core 16 | implementation mn.kotlinx.coroutines.jdk8 17 | } 18 | 19 | tasks.named('test') { 20 | systemProperties['junit.jupiter.execution.parallel.enabled'] = true 21 | maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 22 | forkEvery = 1 23 | } 24 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/config/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Pulsar client configurations. 18 | * @author Haris Secic 19 | * @since 1.0 20 | */ 21 | package io.micronaut.pulsar.config; 22 | -------------------------------------------------------------------------------- /src/main/docs/guide/tls.adoc: -------------------------------------------------------------------------------- 1 | == Transport encryption 2 | To use transport encryption you must configure proper connection string as well as provide necessary information about 3 | certificate path, ciphers, and protocols as shown in example below. 4 | 5 | [configuration] 6 | ---- 7 | pulsar: 8 | service-url: 'pulsar+ssl://localhost:6651' 9 | tls-cert-file-path: 'path/to/cert.pem' 10 | tls-ciphers: 11 | - TLS_RSA_WITH_AES_256_GCM_SHA384 12 | - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 13 | tls-protocols: 14 | - TLSv1.3 15 | - TLSv1.2 16 | ---- 17 | 18 | It's necessary to ensure that both server and client certificates match with supported ciphers and protocols. By default 19 | hostname is not verified but can be enabled by using `tls-verify-hostname: true` 20 | 21 | TLS as authentication method is not yet supported. 22 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Entry point for Micronaut Pulsar integration. 18 | * @author Haris Secic 19 | * @since 1.0 20 | */ 21 | package io.micronaut.pulsar; 22 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/processor/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Pulsar annotations processors. 18 | * @author Haris Secic 19 | * @since 1.0 20 | */ 21 | package io.micronaut.pulsar.processor; 22 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/json/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * JSON data type processor for Pulsar. 18 | * @author Haris Secic 19 | * @since 1.0 20 | */ 21 | package io.micronaut.pulsar.schemas.json; 22 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | authors: 4 | - micronaut-build 5 | categories: 6 | - title: Breaking Changes 🛠 7 | labels: 8 | - 'type: breaking' 9 | - title: New Features 🎉 10 | labels: 11 | - 'type: enhancement' 12 | - title: Bug Fixes 🐞 13 | labels: 14 | - 'type: bug' 15 | - title: Improvements ⭐ 16 | labels: 17 | - 'type: improvement' 18 | - title: Docs 📖 19 | labels: 20 | - 'type: docs' 21 | - title: Dependency updates 🚀 22 | labels: 23 | - 'type: dependency-upgrade' 24 | - 'dependency-upgrade' 25 | - title: Regressions 🧐 26 | labels: 27 | - 'type: regression' 28 | - title: GraalVM 🏆 29 | labels: 30 | - 'relates-to: graal' 31 | - title: Other Changes 💡 32 | labels: 33 | - "*" 34 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/events/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Custom Events for Pulsar consumers, readers, and producers. 18 | * @author Haris Secic 19 | * @since 1.0 20 | */ 21 | package io.micronaut.pulsar.events; 22 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Annotations for Pulsar consumers, producers, and readers. 18 | * @author Haris Secic 19 | * @since 1.0 20 | */ 21 | package io.micronaut.pulsar.annotation; 22 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/intercept/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Interceptors for methods annotated with Pulsar annotations. 18 | * @author Haris Secic 19 | * @since 1.0 20 | */ 21 | package io.micronaut.pulsar.intercept; 22 | -------------------------------------------------------------------------------- /pulsar-multitenant/src/main/java/io/micronaut/pulsar/processor/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Pulsar annotations processors for multitenancy. 18 | * @author Haris Secic 19 | * @since 1.2.0. 20 | */ 21 | package io.micronaut.pulsar.processor; 22 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-shared-module/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'groovy' 4 | } 5 | 6 | dependencies { 7 | api projects.micronautPulsar 8 | api mnTest.micronaut.test.spock 9 | api mn.groovy 10 | 11 | runtimeOnly mn.micronaut.http.server.netty 12 | runtimeOnly mn.micronaut.runtime 13 | 14 | implementation mnGrpc.micronaut.protobuff.support 15 | implementation mnReactor.micronaut.reactor 16 | implementation mnTest.micronaut.test.spock 17 | implementation(platform(mnTestResources.boms.testcontainers)) 18 | implementation libs.testcontainers.pulsar 19 | implementation libs.conscrypt.openjdk 20 | 21 | } 22 | 23 | //TODO remove once Micronaut Test ships Spock version compatible with Groovy 5 24 | configurations.all { 25 | resolutionStrategy { 26 | force("org.spockframework:spock-core:2.4-groovy-5.0") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Schema processors for data types by Pulsar consumers, producers, and readers. 18 | * @author Haris Secic 19 | * @since 1.0 20 | */ 21 | package io.micronaut.pulsar.schemas; 22 | -------------------------------------------------------------------------------- /pulsar-multitenant/src/main/java/io/micronaut/pulsar/events/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Custom Events for multitenant Pulsar consumers, readers, and producers. 18 | * @author Haris Secic 19 | * @since 1.2.0. 20 | */ 21 | package io.micronaut.pulsar.events; 22 | -------------------------------------------------------------------------------- /pulsar-multitenant/src/main/java/io/micronaut/pulsar/intercept/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Interceptors for multitenant methods annotated with Pulsar annotations. 18 | * @author Haris Secic 19 | * @since 1.2.0 20 | */ 21 | package io.micronaut.pulsar.intercept; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .DS_Store 3 | target/ 4 | .gradle/ 5 | .idea/ 6 | build/ 7 | !build-logic/src/main/java/io/micronaut/build 8 | !build-logic/src/main/kotlin/io/micronaut/build 9 | classes/ 10 | out/ 11 | *.db 12 | *.log 13 | *.iml 14 | .classpath 15 | .factorypath 16 | bin/ 17 | .settings/ 18 | .project 19 | */test/ 20 | */META-INF/ 21 | *.ipr 22 | *.iws 23 | .kotlintest 24 | */.kotlintest/ 25 | 26 | # ignore resources, are downloaded via a gradle task from micronaut_docs 27 | src/main/docs/resources/css/highlight/*.css 28 | src/main/docs/resources/css/highlight/*.png 29 | src/main/docs/resources/css/highlight/*.jpg 30 | src/main/docs/resources/css/*.css 31 | src/main/docs/resources/js/*.js 32 | src/main/docs/resources/style/*.html 33 | src/main/docs/resources/img/micronaut-logo-white.svg 34 | 35 | # Ignore files generated by test-resources 36 | **/.micronaut/test-resources/ 37 | 38 | # Ignore gradle.properties generated by micronaut-build 39 | /buildSrc/gradle.properties 40 | -------------------------------------------------------------------------------- /pulsar/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.pulsar-module' 3 | } 4 | 5 | dependencies { 6 | annotationProcessor mnValidation.micronaut.validation.processor 7 | 8 | implementation mnValidation.micronaut.validation 9 | 10 | compileOnly mnReactor.micronaut.reactor 11 | compileOnly mn.kotlinx.coroutines.core 12 | compileOnly libs.protobuf.java 13 | compileOnly mnGrpc.micronaut.protobuff.support 14 | 15 | constraints { 16 | implementation("org.asynchttpclient:async-http-client:3.0.4"){ 17 | because("Older versions has a CVE(CVE-2024-53990) Vulnerability ") 18 | } 19 | implementation("com.google.code.gson:gson:2.13.2") { 20 | because("Older versions has a CVE(CVE-2025-53864) Vulnerability ") 21 | } 22 | implementation("com.fasterxml.jackson.core:jackson-core:2.20.1") { 23 | because("Older versions has a CVE(CVE-2025-52999) Vulnerability ") 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/events/PulsarFailureEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.events; 17 | 18 | /** 19 | * Event raised on failure made by Pulsar client connection or reader, consumer, or producer errors. 20 | * 21 | * @author Haris Secic 22 | * @since 1.0 23 | */ 24 | public interface PulsarFailureEvent { 25 | String getReason(); 26 | } 27 | -------------------------------------------------------------------------------- /doc-examples/example-kotlin/src/test/kotlin/example/ConsumerProducer.kt: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import io.micronaut.pulsar.annotation.PulsarConsumer 4 | import io.micronaut.pulsar.annotation.PulsarProducer 5 | import io.micronaut.pulsar.annotation.PulsarSubscription 6 | import org.apache.pulsar.client.api.SubscriptionType 7 | 8 | @PulsarSubscription(subscriptionName = "pulsar-ktest-subscription", subscriptionType = SubscriptionType.Shared) // <1> 9 | open class ConsumerProducer { // <2> 10 | 11 | @PulsarConsumer(topic = "persistent://public/default/messages-kotlin-docs", consumerName = "shared-consumer-ktester") // <3> 12 | suspend fun messagePrinter(message: String) { // <4> 13 | val changed = report(message) 14 | //... 15 | } 16 | 17 | 18 | @PulsarProducer(topic = "persistent://public/default/reports-kotlin-docs", producerName = "report-producer-kotlin") // <5> 19 | open suspend fun report(message: String): String { // <6> 20 | return "Reporting message '$message'" // <7> 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/docs/guide/releaseHistory.adoc: -------------------------------------------------------------------------------- 1 | For this project, you can find a list of releases (with release notes) here: 2 | 3 | https://github.com/{githubSlug}/releases[https://github.com/{githubSlug}/releases] 4 | 5 | ==== 2.0.0 6 | * Java 17 baseline 7 | * Micronaut 4.0.0 minimum version 8 | * Remove shutdown on subscribe error 9 | * Bug fixes 10 | * Apache Pulsar Java Client dependency 3.0.0 11 | 12 | ==== 1.3.0 13 | 14 | * Dependency updates 15 | * Micronaut 3.8.0 16 | 17 | ==== 1.2.0 18 | 19 | * Added multitenancy support module 20 | 21 | ==== 1.1.0 22 | 23 | * Micronaut 3.2.3 minimum version 24 | * Using new JSON mapper 25 | * Key-Value messages support 26 | * Message Headers support 27 | * Protobuf native messages support using micronaut protobuf dependency 28 | * Apache Pulsar Java Client dependency 2.9.1 29 | 30 | ==== 1.0.0 31 | 32 | * Micronaut 2.1.3 minimum version 33 | * Authorization via JWT 34 | * Service URL Provider resolver 35 | * Basic support for consumers 36 | * Basic support for producers 37 | * Basic support for reader 38 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/src/test/groovy/io/micronaut/pulsar/config/TopicNameValidatorTest.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.pulsar.config 2 | 3 | import spock.lang.Specification 4 | 5 | import java.util.regex.Pattern 6 | 7 | class TopicNameValidatorTest extends Specification { 8 | 9 | void "should allow valid topic names"() { 10 | given: 11 | def compiled = Pattern.compile(AbstractPulsarConfiguration.TOPIC_NAME_VALIDATOR) 12 | def validTopics = ["tenant/namespace/topic","tenant/namespace/topic.with.dots","tenant/namespace/topic-with-dashes", "tenant/namespace/Topic-combo.doTs-and.Dashes"] 13 | 14 | expect: 15 | validTopics.every {it.matches(compiled)} 16 | } 17 | 18 | void "should prevent invalid topic names"() { 19 | given: 20 | def compiled = Pattern.compile(AbstractPulsarConfiguration.TOPIC_NAME_VALIDATOR) 21 | def validTopics = ["tenant/namespace/topic*","tenant/namespace/1+2"] 22 | 23 | expect: 24 | validTopics.every {!it.matches(compiled)} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | plugins { 9 | id 'io.micronaut.build.shared.settings' version '8.0.0-M12' 10 | } 11 | 12 | enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS' 13 | 14 | micronautBuild { 15 | useStandardizedProjectNames = true 16 | importMicronautCatalog() 17 | importMicronautCatalog("micronaut-grpc") 18 | importMicronautCatalog("micronaut-multitenancy") 19 | importMicronautCatalog("micronaut-reactor") 20 | importMicronautCatalog("micronaut-serde") 21 | importMicronautCatalog("micronaut-test-resources") 22 | importMicronautCatalog("micronaut-validation") 23 | } 24 | 25 | rootProject.name = 'pulsar-parent' 26 | 27 | include 'pulsar' 28 | include 'pulsar-bom' 29 | include 'pulsar-multitenant' 30 | include 'test-suite:test-pulsar-shared-module' 31 | include 'test-suite:test-pulsar-module' 32 | include 'test-suite:test-pulsar-multitenant-module' 33 | include 'doc-examples:example-java' 34 | include 'doc-examples:example-kotlin' 35 | include 'doc-examples:example-groovy' 36 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/processor/MessageListenerResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.processor; 17 | 18 | import org.apache.pulsar.client.api.MessageListener; 19 | 20 | /** 21 | * Provides flexibility to implement {@link org.apache.pulsar.client.api.MessageListener} as beans 22 | * while still being able to override default listener resolver for Pulsar Consumers. 23 | * 24 | * @author Haris Secic 25 | * @since 1.0 26 | */ 27 | public interface MessageListenerResolver extends MessageListener { 28 | } 29 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thanks for reporting an issue, please review the task list below before submitting the 2 | issue. Your issue report will be closed if the issue is incomplete and the below tasks not completed. 3 | 4 | NOTE: If you are unsure about something and the issue is more of a question a better place to ask questions is on Stack Overflow (https://stackoverflow.com/tags/micronaut) or Gitter (https://gitter.im/micronautfw/). DO NOT use the issue tracker to ask questions. 5 | 6 | ### Task List 7 | 8 | - [ ] Steps to reproduce provided 9 | - [ ] Stacktrace (if present) provided 10 | - [ ] Example that reproduces the problem uploaded to Github 11 | - [ ] Full description of the issue provided (see below) 12 | 13 | ### Steps to Reproduce 14 | 15 | 1. TODO 16 | 2. TODO 17 | 3. TODO 18 | 19 | ### Expected Behaviour 20 | 21 | Tell us what should happen 22 | 23 | ### Actual Behaviour 24 | 25 | Tell us what happens instead 26 | 27 | ### Environment Information 28 | 29 | - **Operating System**: TODO 30 | - **Micronaut Version:** TODO 31 | - **JDK Version:** TODO 32 | 33 | ### Example Application 34 | 35 | - TODO: link to github repository with example that reproduces the issue 36 | 37 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/MessageKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.annotation; 17 | 18 | import io.micronaut.core.bind.annotation.Bindable; 19 | 20 | import java.lang.annotation.*; 21 | 22 | /** 23 | * Parameter level annotation to indicate which parameter is bound to the Pulsar message key. 24 | * 25 | * @author Haris Secic 26 | * @since 1.0 27 | */ 28 | @Documented 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @Target({ElementType.PARAMETER}) 31 | @Bindable 32 | @Inherited 33 | public @interface MessageKey { 34 | } 35 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/MessageProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.annotation; 17 | 18 | import io.micronaut.core.bind.annotation.Bindable; 19 | 20 | import java.lang.annotation.*; 21 | 22 | /** 23 | * Parameter level annotation to indicate which parameter is bound to the message properties map. 24 | * 25 | * @author Haris Secic 26 | * @since 1.1 27 | */ 28 | @Documented 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @Target({ElementType.PARAMETER}) 31 | @Bindable 32 | public @interface MessageProperties { 33 | } 34 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/events/ConsumerSubscribedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.events; 17 | 18 | import org.apache.pulsar.client.api.Consumer; 19 | 20 | /** 21 | * Produced when Pulsar consumer connects to a broker and starts listening to specified topics. 22 | * 23 | * @since 1.0 24 | * @author Haris Secic 25 | */ 26 | public final class ConsumerSubscribedEvent { 27 | 28 | private final Consumer consumer; 29 | 30 | public ConsumerSubscribedEvent(Consumer consumer) { 31 | this.consumer = consumer; 32 | } 33 | 34 | public Consumer getConsumer() { 35 | return consumer; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /doc-examples/example-groovy/src/test/groovy/example/ConsumerProducer.groovy: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import io.micronaut.pulsar.annotation.PulsarConsumer; 4 | import io.micronaut.pulsar.annotation.PulsarProducer; 5 | import io.micronaut.pulsar.annotation.PulsarSubscription; 6 | import org.apache.pulsar.client.api.SubscriptionType; 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.ExecutionException; 10 | 11 | @PulsarSubscription(subscriptionName = "pulsar-jtest-subscription", subscriptionType = SubscriptionType.Shared) // <1> 12 | class ConsumerProducer { // <2> 13 | @PulsarConsumer(topic = "persistent://public/default/messages-groovy-docs", consumerName = "shared-consumer-gtester") // <3> 14 | void messagePrinter(String message) { // <4> 15 | try { 16 | String changed = report(message).get(); 17 | } catch (InterruptedException | ExecutionException e) { 18 | e.printStackTrace(); 19 | } 20 | //... 21 | } 22 | 23 | 24 | @PulsarProducer(topic = "persistent://public/default/reports-groovy-docs", producerName = "report-producer-groovy") // <5> 25 | CompletableFuture report(String message) { // <6> 26 | return CompletableFuture.supplyAsync(() -> String.format("Reporting message %s", message)); // <7> 27 | } 28 | } -------------------------------------------------------------------------------- /doc-examples/example-java/src/test/java/example/ConsumerProducer.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import io.micronaut.pulsar.annotation.PulsarConsumer; 4 | import io.micronaut.pulsar.annotation.PulsarProducer; 5 | import io.micronaut.pulsar.annotation.PulsarSubscription; 6 | import org.apache.pulsar.client.api.SubscriptionType; 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.ExecutionException; 10 | 11 | @PulsarSubscription(subscriptionName = "pulsar-jtest-subscription", subscriptionType = SubscriptionType.Shared) // <1> 12 | public class ConsumerProducer { // <2> 13 | @PulsarConsumer(topic = "persistent://public/default/messages-java-docs", consumerName = "shared-consumer-jtester") // <3> 14 | public void messagePrinter(String message) { // <4> 15 | try { 16 | String changed = report(message).get(); 17 | } catch (InterruptedException | ExecutionException e) { 18 | e.printStackTrace(); 19 | } 20 | //... 21 | } 22 | 23 | 24 | @PulsarProducer(topic = "persistent://public/default/reports-java-docs", producerName = "report-producer-java") // <5> 25 | public CompletableFuture report(String message) { // <6> 26 | return CompletableFuture.supplyAsync(() -> String.format("Reporting message %s", message)); // <7> 27 | } 28 | } -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/PulsarReaderRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar; 17 | 18 | import org.apache.pulsar.client.api.Reader; 19 | 20 | import java.util.Collection; 21 | 22 | /** 23 | * Keeps track of all created Pulsar readers. 24 | * 25 | * @author Haris Secic 26 | * @since 1.0 27 | */ 28 | public interface PulsarReaderRegistry { 29 | 30 | /** 31 | * If not specified explicitly, reader name will default to property/field name. 32 | * 33 | * @param identifier unique identifier for a reader 34 | * @return Pulsar Reader if found for a given name 35 | */ 36 | Reader getReader(String identifier); 37 | 38 | /** 39 | * @return all registered Pulsar Readers 40 | */ 41 | Collection> getReaders(); 42 | } 43 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/PulsarConsumerRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar; 17 | 18 | import org.jspecify.annotations.NonNull; 19 | import org.apache.pulsar.client.api.Consumer; 20 | 21 | import java.util.Map; 22 | import java.util.Set; 23 | 24 | /** 25 | * A registry for created Pulsar consumers. 26 | * 27 | * @author Haris Secic 28 | * @since 1.0 29 | */ 30 | public interface PulsarConsumerRegistry { 31 | 32 | Map> getConsumers(); 33 | 34 | Consumer getConsumer(@NonNull String id); 35 | 36 | boolean consumerExists(@NonNull String id); 37 | 38 | Set getConsumerIds(); 39 | 40 | boolean isPaused(@NonNull String id); 41 | 42 | void pause(@NonNull String id); 43 | 44 | void resume(@NonNull String id); 45 | } 46 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/src/test/groovy/io/micronaut/pulsar/dynamic/DynamicReader.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.dynamic 17 | 18 | import io.micronaut.context.annotation.Requires 19 | import io.micronaut.pulsar.DynamicTenantTopicSpec 20 | import io.micronaut.pulsar.annotation.PulsarReader 21 | import io.micronaut.pulsar.annotation.PulsarReaderClient 22 | import org.apache.pulsar.client.api.Message 23 | 24 | @Requires(property = 'spec.name', value = 'DynamicTenantTopicSpec') 25 | @PulsarReaderClient 26 | interface DynamicReader { 27 | 28 | @PulsarReader(topic = DynamicTenantTopicSpec.PULSAR_DYNAMIC_TENANT_TEST_TOPIC, 29 | readTimeout = 60, 30 | readerName = "fake-reader", 31 | startMessageLatest = false) 32 | Message read(); 33 | } 34 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/protobuf/ProtobufWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.protobuf; 17 | 18 | import io.micronaut.protobuf.codec.ProtobufferCodec; 19 | import org.apache.pulsar.client.api.schema.SchemaWriter; 20 | 21 | /** 22 | * Protobuf Schema Writer to allow using {@link ProtobufferCodec} from Micronaut. 23 | * 24 | * @param POJO type to process. 25 | * @author Haris Secic 26 | * @since 1.1.0 27 | */ 28 | public final class ProtobufWriter implements SchemaWriter { 29 | 30 | private final ProtobufferCodec codec; 31 | 32 | public ProtobufWriter(ProtobufferCodec codec) { 33 | this.codec = codec; 34 | } 35 | 36 | @Override 37 | public byte[] write(Object message) { 38 | return codec.encode(message); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/PulsarServiceUrlProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.annotation; 17 | 18 | import jakarta.inject.Qualifier; 19 | 20 | import java.lang.annotation.Documented; 21 | import java.lang.annotation.Retention; 22 | 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Marks a class that contains a method to resolve the Pulsar service url. The 27 | * annotated class must implement {@link org.apache.pulsar.client.api.ServiceUrlProvider}. 28 | * Also servers as a qualifier for injection of ServiceUrlProvider bean. 29 | * 30 | * @author Haris Secic 31 | * @since 1.0 32 | */ 33 | @Documented 34 | @Retention(RUNTIME) 35 | @Qualifier 36 | public @interface PulsarServiceUrlProvider { 37 | //Just a distinguished annotation for PulsarServiceProvider 38 | } 39 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/src/test/groovy/io/micronaut/pulsar/dynamic/ProducerDynamicTenantTopicTester.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.dynamic 17 | 18 | import io.micronaut.context.annotation.Requires 19 | import io.micronaut.pulsar.DynamicTenantTopicSpec 20 | import io.micronaut.pulsar.annotation.PulsarProducer 21 | import io.micronaut.pulsar.annotation.PulsarProducerClient 22 | import jakarta.inject.Singleton 23 | import org.apache.pulsar.client.api.MessageId 24 | 25 | @Requires(property = 'spec.name', value = 'DynamicTenantTopicSpec') 26 | @PulsarProducerClient 27 | interface ProducerDynamicTenantTopicTester { 28 | 29 | @PulsarProducer( 30 | topic = DynamicTenantTopicSpec.PULSAR_DYNAMIC_TENANT_TEST_TOPIC, 31 | producerName = 'dynamic-topic-producer') 32 | MessageId send(String message) 33 | } 34 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/PulsarProducerClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.annotation; 17 | 18 | import io.micronaut.aop.Introduction; 19 | import jakarta.inject.Singleton; 20 | 21 | import java.lang.annotation.Documented; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.Target; 24 | 25 | import static java.lang.annotation.ElementType.TYPE; 26 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 27 | 28 | /** 29 | * Marks a type as a bean containing producer methods. 30 | * To avoid mixing up with PulsarClient interface from official library "Producer" part was introduced. 31 | * 32 | * @author Haris Secic 33 | * @since 1.0 34 | */ 35 | @Documented 36 | @Retention(RUNTIME) 37 | @Introduction 38 | @Singleton 39 | @Target({TYPE}) 40 | public @interface PulsarProducerClient { 41 | } 42 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/PulsarReaderClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.annotation; 17 | 18 | import io.micronaut.aop.Introduction; 19 | import jakarta.inject.Singleton; 20 | 21 | import java.lang.annotation.Documented; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.Target; 24 | 25 | import static java.lang.annotation.ElementType.TYPE; 26 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 27 | 28 | /** 29 | * Marks a type as a bean containing reader methods. To avoid problems with the 30 | * {@link PulsarProducerClient} due to possible later enhancement of features it was separated from 31 | * it. 32 | * 33 | * @author Haris Secic 34 | * @since 1.2.0 35 | */ 36 | @Documented 37 | @Retention(RUNTIME) 38 | @Introduction 39 | @Singleton 40 | @Target({TYPE}) 41 | public @interface PulsarReaderClient { 42 | } 43 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/avro/AvroSchemaResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.avro; 17 | 18 | import io.micronaut.pulsar.schemas.SchemaResolver; 19 | import jakarta.inject.Named; 20 | import jakarta.inject.Singleton; 21 | import org.apache.pulsar.client.api.Schema; 22 | import org.apache.pulsar.client.impl.schema.AvroSchema; 23 | import org.apache.pulsar.client.impl.schema.SchemaDefinitionBuilderImpl; 24 | 25 | /** 26 | * AVRO schema resolver. 27 | * 28 | * @author Haris Secic 29 | * @since 1.2.1 30 | */ 31 | @Singleton 32 | @Named(SchemaResolver.AVRO_SCHEMA_NAME) 33 | public final class AvroSchemaResolver implements SchemaResolver { 34 | @Override 35 | public Schema forArgument(final Class pojo) { 36 | return AvroSchema.of(new SchemaDefinitionBuilderImpl() 37 | .withPojo(pojo) 38 | .build()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pulsar-multitenant/src/main/java/io/micronaut/pulsar/events/PulsarTenantDiscoveredEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.events; 17 | 18 | import org.jspecify.annotations.NonNull; 19 | import java.io.Serializable; 20 | 21 | /** 22 | * Simple event to publish on new tenant discovery. Event is used by 23 | * {@link io.micronaut.pulsar.processor.PulsarMultiTenantConsumerProcessor} in case tenants were nat available at the 24 | * time of initialization of consumer bean(s). It's possible to pre-define list of tenant in some way and 25 | * 26 | * @author Haris 27 | * @since 1.2.0 28 | */ 29 | public final class PulsarTenantDiscoveredEvent { 30 | private final Serializable tenant; 31 | 32 | public PulsarTenantDiscoveredEvent(@NonNull final Serializable tenant) { 33 | this.tenant = tenant; 34 | } 35 | 36 | public Serializable getTenant() { 37 | return tenant; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/docs/guide/reader.adoc: -------------------------------------------------------------------------------- 1 | == Readers 2 | 3 | Pulsar supports both "Consumers" and "Readers". More can be read in their 4 | https://pulsar.apache.org/docs/en/concepts-clients/#reader-interface[documentation] 5 | 6 | == Creating readers 7 | 8 | To initialize a reader, declare a field annotated with `@PulsarReader` inside any bean or as a constructor argument. 9 | 10 | snippet::example.ReaderExample[project-base="doc-examples/example", indent="0"] 11 | <1> Reader annotation with the topic and the reader name 12 | <2> Reader must be of type api:org.apache.pulsar.client.api.Reader 13 | <3> Using readAsync requires `CompletableFeature` or in Kotlin awaiting is possible 14 | <4> Calling the read will move the cursor to the next message or give null in case there are no more messages 15 | 16 | Reader `name` can be autogenerated but `topic` argument must be set. Reader injections cause blocking behaviour as 17 | Reader instances will start creation and wait until Pulsar replies with a successful status. Reader `name` defaults 18 | to field or argument name for injection points or method name for method annotated reader. 19 | 20 | === KeyValue readers 21 | 22 | In order to use KeyValue with a reader, reader argument type must be of `org.apache.pulsar.common.schema.KeyValue`. 23 | It is possible then to set attributes in `@PulsarReader` like `keyType` which defines what type of data 24 | serializer will be used to extract key, and `keyEncoding` which defines how will the key be extracted: `INLINE` - key 25 | is part of the message payload or `SEPARATED` - key is stored as a message key. -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/src/test/groovy/io/micronaut/pulsar/dynamic/FakeClient.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.dynamic 17 | 18 | import io.micronaut.context.annotation.Requires 19 | import io.micronaut.http.HttpHeaders 20 | import io.micronaut.http.annotation.Body 21 | import io.micronaut.http.annotation.Get 22 | import io.micronaut.http.annotation.Header 23 | import io.micronaut.http.annotation.Post 24 | import io.micronaut.http.client.annotation.Client 25 | import org.apache.pulsar.client.api.Message 26 | import reactor.core.publisher.Mono 27 | 28 | @Requires(property = 'spec.name', value = 'DynamicTenantTopicSpec') 29 | @Client('/') 30 | @Header(name = HttpHeaders.USER_AGENT, value = "Micronaut HTTP Client") 31 | interface FakeClient { 32 | @Post('/messages') 33 | String sendMessage(@Header String tenantId, @Body String message); 34 | 35 | @Post("/tenant") 36 | String addTenantConsumer(@Body String tenant); 37 | } 38 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/json/JsonSchemaResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.json; 17 | 18 | import io.micronaut.context.annotation.Requires; 19 | import io.micronaut.json.JsonMapper; 20 | import io.micronaut.pulsar.schemas.SchemaResolver; 21 | import jakarta.inject.Named; 22 | import jakarta.inject.Singleton; 23 | import org.apache.pulsar.client.api.Schema; 24 | 25 | /** 26 | * JSON schema resolver. 27 | * 28 | * @author Haris Secic 29 | * @since 1.1.0 30 | */ 31 | @Singleton 32 | @Named(SchemaResolver.JSON_SCHEMA_NAME) 33 | @Requires(classes = {JsonMapper.class}) 34 | public class JsonSchemaResolver implements SchemaResolver { 35 | 36 | private final JsonMapper mapper; 37 | 38 | public JsonSchemaResolver(final JsonMapper mapper) { 39 | this.mapper = mapper; 40 | } 41 | 42 | @Override 43 | public Schema forArgument(Class pojo) { 44 | return JsonSchema.of(pojo, mapper); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/PulsarProducerRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar; 17 | 18 | import org.jspecify.annotations.NonNull; 19 | import org.apache.pulsar.client.api.Producer; 20 | 21 | import java.util.Map; 22 | import java.util.Set; 23 | 24 | /** 25 | * A registry of managed {@link Producer} instances key by id and type. 26 | * 27 | * @author Haris Secic 28 | * @since 1.0 29 | */ 30 | public interface PulsarProducerRegistry { 31 | 32 | /** 33 | * Get all managed producers. 34 | * @return List of managed producers. 35 | */ 36 | Map> getProducers(); 37 | 38 | /** 39 | * Get single managed producer by its name. 40 | * @param id unique identifier 41 | * @return Pulsar producer by given name 42 | */ 43 | Producer getProducer(@NonNull String id); 44 | 45 | /** 46 | * Get all managed producer identifiers. 47 | * @return List of producer names representing their identifiers in registry. 48 | */ 49 | Set getProducerIds(); 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Do not edit this file directly. Instead, go to: 2 | # 3 | # https://github.com/micronaut-projects/micronaut-project-template/tree/master/.github/workflows 4 | # 5 | # and edit them there. Note that it will be sync'ed to all the Micronaut repos 6 | name: Publish snapshot release 7 | on: [workflow_dispatch] 8 | jobs: 9 | build: 10 | if: github.repository != 'micronaut-projects/micronaut-project-template' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Remove system JDKs 14 | run: | 15 | sudo rm -rf /usr/lib/jvm/* 16 | unset JAVA_HOME 17 | export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/usr/lib/jvm' | paste -sd:) 18 | - uses: actions/checkout@v6 19 | - uses: actions/cache@v5 20 | with: 21 | path: ~/.gradle/caches 22 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 23 | restore-keys: | 24 | ${{ runner.os }}-gradle- 25 | - name: Set up JDK 26 | uses: actions/setup-java@v5 27 | with: 28 | distribution: 'temurin' 29 | java-version: | 30 | 21 31 | 25 32 | - name: Publish to Sonatype Snapshots 33 | if: success() 34 | env: 35 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 36 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 37 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 38 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 39 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 40 | run: ./gradlew publishToSonatype --no-daemon 41 | -------------------------------------------------------------------------------- /src/main/docs/guide/quickStart.adoc: -------------------------------------------------------------------------------- 1 | To configure the Apache Pulsar client, first add the `micronaut-pulsar` module dependency: 2 | 3 | dependency:micronaut-pulsar[groupId="io.micronaut.pulsar"] 4 | NOTE: default serializers & deserializers are JSON and will use mn-serde module so don't forget to include those 5 | as well if you will not switch manually to BYTES or other parsers 6 | dependency:micronaut-serde-jackson[groupId="io.micronaut.serde"] 7 | 8 | Then configure the URI of the Pulsar cluster or standalone server to communicate with: 9 | 10 | [configuration] 11 | .Configuring `pulsar.service-url` 12 | ---- 13 | pulsar: 14 | service-url: pulsar://localhost:6650 15 | ---- 16 | NOTE: As this module is based on the official Java client from Apache Pulsar, see the link:https://pulsar.apache.org/docs/en/client-libraries-java/#connection-urls[official documentation] for detailed information on service URL format. 17 | 18 | Alternatively, `pulsar.service-url-provider` can be set using either `@kotlinexample.PulsarServiceUrlProvider` or by setting the provider name: 19 | 20 | [configuration] 21 | ---- 22 | pulsar: 23 | service-url-provider: BeanName 24 | ---- 25 | 26 | in which case the implementing class must be annotated with `@Named` with value equal to the one in YAML. In both cases, the bean must implement the `org.apache.pulsar.client.api.ServiceUrlProvider` interface. 27 | 28 | After configuring the Pulsar cluster URL, the Micronaut Pulsar module will be able to produce a bean of type `org.apache.pulsar.client.api.PulsarClient`. This bean will be a `Singleton` from which all producers and consumers can be created. Since `PulsarClient` supports an URL provider which can switch URLs to clusters on demand, there's no need to have multiple clients. 29 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/json/JsonWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.json; 17 | 18 | import io.micronaut.json.JsonMapper; 19 | import org.apache.pulsar.client.api.SchemaSerializationException; 20 | import org.apache.pulsar.client.api.schema.SchemaWriter; 21 | 22 | import java.io.IOException; 23 | 24 | /** 25 | * JSON Schema Writer to allow using {@link JsonMapper} from Micronaut instead of shaded one in Pulsar library. 26 | * 27 | * @param POJO type to process. 28 | * @author Haris Secic 29 | * @since 1.0 30 | */ 31 | public final class JsonWriter implements SchemaWriter { 32 | 33 | private final JsonMapper mapper; 34 | 35 | /** 36 | * @param mapper new json mapper 37 | * @since 1.1.0 38 | */ 39 | public JsonWriter(JsonMapper mapper) { 40 | this.mapper = mapper; 41 | } 42 | 43 | @Override 44 | public byte[] write(T message) { 45 | try { 46 | return mapper.writeValueAsBytes(message); 47 | } catch (IOException e) { 48 | throw new SchemaSerializationException(e); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/events/ProducerSubscriptionFailedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.events; 17 | 18 | /** 19 | * Raise error event on failed producer subscription. 20 | * 21 | * @author Haris Secic 22 | * @since 1.0 23 | */ 24 | public final class ProducerSubscriptionFailedEvent implements PulsarFailureEvent { 25 | 26 | private final String producerName; 27 | private final String reason; 28 | private final Throwable sourceError; 29 | 30 | public ProducerSubscriptionFailedEvent(String producerName, Throwable sourceError) { 31 | this.producerName = producerName; 32 | this.sourceError = sourceError; 33 | String error = ""; 34 | if (null != sourceError) { 35 | error = sourceError.getMessage(); 36 | } 37 | this.reason = String.format("Producer %s failed to connect. %s", producerName, error); 38 | } 39 | 40 | @Override 41 | public String getReason() { 42 | return reason; 43 | } 44 | 45 | public Throwable getSourceError() { 46 | return sourceError; 47 | } 48 | 49 | public String getProducerName() { 50 | return producerName; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-shared-module/src/test/resources/broker.key-pk8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9/auywhjDEZrQ 3 | 1TRs+lB72eNubD/84IOqThut8DmUQ6m1f3vdiEbpYQTaFPhQR/kL/3/wHcRQicPR 4 | RBXZB4YvVEQHIR3yfz1EW3FvjxCQ1VC2G4aIpoekiMBitBOpd+L9BatPsXh4bnZu 5 | 0wFhjSKtFQzG+WZ6HPBDQ2j5AuJgmzbugpYx6Zv9rlYtGn/kQQJhah/+UI8mdl7B 6 | 9hLNcwBDr7G1Lw1WvZH7k8bY1Lvhj4ZUhlMEi8Gs9QRJt659/ieLg6/LELi8XnJC 7 | v7VCkyyNYw00hfMwhql9/e/XNV1VjMOk8A5BxZZtSmVhDMwAToxvbEK6KZTpXbP+ 8 | w8AFFYlpAgMBAAECggEAHkPyvh89YpXDW9Ea8OFPQOuM12JXLyl00ARxVFwYUA4X 9 | UtpkGP5EjZju9O6yP9e3TLn+33ledJX9o7B86n3QWmnKDHiyFJdAY0tN3r+jm+7f 10 | VKM94zbkFMnUG+Uj3LNEbZohmChlUIoIQ89JQQkCpR/dUTUZpgpGG/D5H/HdrhXE 11 | 8LbZgkI4raB9Y7LKFyb3GxGIvC0Ck3L0CF8peRq5LffF5axuK0C79sCwtkYMrmtn 12 | WObj4ijhtj++mKcFUYIs0OPC9chv5WvIn81k2Si2pQcPXIYYAt7HCUSrrtYJkleg 13 | u5odCc3pUJuP7QfYdHEOW87IlMt87oseuWlf1+IdoQKBgQD6Cl3g/6PJo8OisOhB 14 | uKLlhQo4JVJRbZ5BTXxBKOvttEsvTPBDkTeohawo91jzbrQTaw/ZfddfSvtGY2P6 15 | Mf8/BAOz496mlGLfvdb4uhtQJL/PdOxxEAdB7I0vX4SDkUUK1zkC8EetjXGh5ng1 16 | uG0VTKLDHJ9wyXoZJLwdvpRwHwKBgQDChOigLd2Viu4ofr34FzCxgbqjcOIF/7i9 17 | pKetWE+ya6qkmmhIHKYaUbITaUM2yDA/mzwYZaGnQltQn8PELGzNxnS1Ppp9URZ+ 18 | 0HuNfPQ8ljR+l6TGXrZ7LxGvUJxwajWZVl/t/j54gZDz6v2ZUHmfn57SgceyGc58 19 | VZ0VZUE1dwKBgH4wiy6JdezGxG4wIBdDCpsRKJYrZ78Gto64f1QXRfKCQkuN5dgD 20 | yvY2+Cr4oiDBJwqBxs757p7+JZBhisxzPSFdckzwXKbvTnMiyQJgUnaBBXi6xrVM 21 | 5IzFyH83Epaf0xtkgTZVc6dIMigGO3bJK+xK7/3OTT1LwV49Jt9QFnP7AoGABuFI 22 | kRHOOpHVZp1E/tdjI4QhI9LES84FrL98Jsdu2jKwsG29rn9E6L1m/QTGGdPu0PEe 23 | qQY/N3lDXZCpDfE8T/LrAdYbBL+vegDJvfFjehkd9jRK4bFL5wN9LqQ9RESdbLT/ 24 | Y52QfBapF9eB7MBJ7PlqVqu8cnIzOXaVvcH9EnUCgYB8SW84JCXPvfrxwMQVQy0u 25 | iZbIiIqOPPiv3BeiFvLtYp/4M6DayEgaMDFWbwrgpuUtWMhT1Bm+mCbYE8AouxWT 26 | 2TbIqpChsH3pm9/KOwVKx/YzovY7rn324GU8GvN2hwKafbq8C9SfoVZgzdnn0bo3 27 | iZswV36CfzCvxpbHgLnaNA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/processor/ListenerKotlinHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.processor; 17 | 18 | import io.micronaut.inject.ExecutableMethod; 19 | import kotlin.coroutines.EmptyCoroutineContext; 20 | import kotlinx.coroutines.BuildersKt; 21 | import kotlinx.coroutines.CoroutineScope; 22 | import kotlinx.coroutines.CoroutineScopeKt; 23 | import kotlinx.coroutines.CoroutineStart; 24 | 25 | import java.util.Arrays; 26 | 27 | /** 28 | * Kotlin helper class that bridges Java calls from Pulsar Java library into suspend calls to Kotlin. 29 | * 30 | * @author Haris Secic 31 | * @since 1.0 32 | */ 33 | public final class ListenerKotlinHelper { 34 | public static Object run(final ExecutableMethod method, final T invoker, final Object... args) { 35 | final Object[] allArgs = Arrays.copyOf(args, args.length + 1); 36 | final CoroutineScope scope = CoroutineScopeKt.CoroutineScope(EmptyCoroutineContext.INSTANCE); 37 | return BuildersKt.launch(scope, EmptyCoroutineContext.INSTANCE, CoroutineStart.DEFAULT, (s, continuation) -> { 38 | allArgs[args.length] = continuation; 39 | return method.invoke(invoker, allArgs); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/central-sync.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Do not edit this file directly. Instead, go to: 2 | # 3 | # https://github.com/micronaut-projects/micronaut-project-template/tree/master/.github/workflows 4 | # 5 | # and edit them there. Note that it will be sync'ed to all the Micronaut repos 6 | name: Maven Central Sync 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | release_version: 11 | description: 'Release version (eg: 1.2.3)' 12 | required: true 13 | jobs: 14 | central-sync: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Remove system JDKs 18 | run: | 19 | sudo rm -rf /usr/lib/jvm/* 20 | unset JAVA_HOME 21 | export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/usr/lib/jvm' | paste -sd:) 22 | - name: Checkout repository 23 | uses: actions/checkout@v6 24 | with: 25 | ref: v${{ github.event.inputs.release_version }} 26 | - uses: gradle/actions/wrapper-validation@v5 27 | - name: Set up JDK 28 | uses: actions/setup-java@v5 29 | with: 30 | distribution: 'temurin' 31 | java-version: | 32 | 21 33 | 25 34 | - name: Publish to Sonatype OSSRH 35 | env: 36 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 37 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 38 | GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} 39 | GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} 40 | GPG_FILE: ${{ secrets.GPG_FILE }} 41 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 42 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 43 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 44 | run: | 45 | echo $GPG_FILE | base64 -d > secring.gpg 46 | ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository 47 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/src/test/groovy/io/micronaut/pulsar/dynamic/ConsumerDynamicTenantTopicTester.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.dynamic 17 | 18 | import io.micronaut.context.annotation.Requires 19 | import io.micronaut.messaging.annotation.MessageBody 20 | import io.micronaut.pulsar.DynamicTenantTopicSpec 21 | import io.micronaut.pulsar.annotation.PulsarConsumer 22 | import io.micronaut.pulsar.annotation.PulsarSubscription 23 | import org.apache.pulsar.client.api.Consumer 24 | import org.apache.pulsar.client.api.Message 25 | import spock.util.concurrent.BlockingVariables 26 | 27 | @Requires(property = 'spec.name', value = 'DynamicTenantTopicSpec') 28 | @PulsarSubscription(subscriptionName = "subscriber-dynamic") 29 | class ConsumerDynamicTenantTopicTester { 30 | BlockingVariables blockers 31 | 32 | @PulsarConsumer( 33 | topic = DynamicTenantTopicSpec.PULSAR_DYNAMIC_TENANT_TEST_TOPIC, 34 | consumerName = 'dynamic-topic-consumer', 35 | subscribeAsync = false) 36 | void topicListener(@MessageBody Message message, Consumer consumer) { 37 | if (null == blockers) { 38 | return 39 | } 40 | blockers.setProperty(message.getMessageId().toString(), message.getValue() + " " + consumer.topic.split("/")[2]) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/protobuf/ProtobufReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.protobuf; 17 | 18 | import io.micronaut.core.type.Argument; 19 | import io.micronaut.protobuf.codec.ProtobufferCodec; 20 | import org.apache.pulsar.client.api.schema.SchemaReader; 21 | 22 | import java.io.InputStream; 23 | import java.util.Arrays; 24 | 25 | /** 26 | * Protobuf Schema Reader to allow using {@link ProtobufferCodec} from Micronaut. 27 | * 28 | * @param POJO type to process. 29 | * @author Haris Secic 30 | * @since 1.1.0 31 | */ 32 | public class ProtobufReader implements SchemaReader { 33 | 34 | private final ProtobufferCodec codec; 35 | private final Argument type; 36 | 37 | public ProtobufReader(final ProtobufferCodec codec, final Argument type) { 38 | this.codec = codec; 39 | this.type = type; 40 | } 41 | 42 | @Override 43 | public T read(byte[] bytes, int offset, int length) { 44 | if (0 == offset && bytes.length == length) { 45 | return codec.decode(type, bytes); 46 | } 47 | final byte[] copy = Arrays.copyOfRange(bytes, offset, length); 48 | return codec.decode(type, copy); 49 | } 50 | 51 | @Override 52 | public T read(InputStream inputStream) { 53 | return codec.decode(type, inputStream); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/protobuf/ProtobufSchemaResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.protobuf; 17 | 18 | import com.google.protobuf.Message; 19 | import io.micronaut.context.annotation.Requires; 20 | import io.micronaut.messaging.exceptions.MessageListenerException; 21 | import io.micronaut.protobuf.codec.ProtobufferCodec; 22 | import io.micronaut.pulsar.schemas.SchemaResolver; 23 | import jakarta.inject.Named; 24 | import jakarta.inject.Singleton; 25 | import org.apache.pulsar.client.api.Schema; 26 | 27 | /** 28 | * Protobuf schema resolver. 29 | * 30 | * @author Haris Secic 31 | * @since 1.1.0 32 | */ 33 | @Singleton 34 | @Named(SchemaResolver.PROTOBUF_SCHEMA_NAME) 35 | @Requires(classes = {ProtobufferCodec.class}) 36 | public class ProtobufSchemaResolver implements SchemaResolver { 37 | 38 | private final ProtobufferCodec codec; 39 | 40 | public ProtobufSchemaResolver(final ProtobufferCodec codec) { 41 | this.codec = codec; 42 | } 43 | 44 | @Override 45 | public Schema forArgument(final Class pojo) { 46 | if (!Message.class.isAssignableFrom(pojo)) { 47 | throw new MessageListenerException("Protocol buffers (native) are only supported for types that implement com.google.protobuf.Message"); 48 | } 49 | return ProtobufSchema.of(pojo, codec); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/events/ConsumerSubscriptionFailedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.events; 17 | 18 | import java.util.Optional; 19 | 20 | /** 21 | * Raise error event on failed subscription. Gives more flexibility than throwing exceptions. Developers decide how to 22 | * handle problems with Pulsar connections. 23 | * 24 | * @author Haris Secic 25 | * @since 1.0 26 | */ 27 | public final class ConsumerSubscriptionFailedEvent implements PulsarFailureEvent { 28 | 29 | private final Throwable sourceError; 30 | private final String consumerName; 31 | private final String reason; 32 | 33 | public ConsumerSubscriptionFailedEvent(Throwable sourceError, String consumerName) { 34 | this.sourceError = sourceError; 35 | this.consumerName = consumerName; 36 | String error = ""; 37 | if (null != sourceError) { 38 | error = sourceError.getMessage(); 39 | } 40 | this.reason = String.format("Consumer %s failed to subscribe. %s", consumerName, error); 41 | } 42 | 43 | public Optional getSourceError() { 44 | return Optional.ofNullable(sourceError); 45 | } 46 | 47 | public String getConsumerName() { 48 | return consumerName; 49 | } 50 | 51 | @Override 52 | public String getReason() { 53 | return reason; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | conscrypt-openjdk = "2.5.2" 4 | kotest-runner = '5.9.1' 5 | protobuf-java = '4.32.1' 6 | micronaut = "5.0.0-M3" 7 | micronaut-platform = '4.10.1' 8 | micronaut-gradle-plugin = '4.6.1' 9 | micronaut-grpc = "4.12.0" 10 | micronaut-multitenancy = "5.8.0" 11 | micronaut-validation = "5.0.0-M1" 12 | micronaut-reactor = "4.0.0-M1" 13 | micronaut-serde = "3.0.0-M1" 14 | micronaut-test-resources = "2.10.1" 15 | 16 | managed-pulsar-client = '3.3.9' 17 | 18 | [libraries] 19 | # Core 20 | micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'micronaut' } 21 | 22 | # Micronaut BOMS 23 | micronaut-grpc = { module = "io.micronaut.grpc:micronaut-grpc-bom", version.ref = "micronaut-grpc" } 24 | micronaut-multitenancy = { module = "io.micronaut.multitenancy:micronaut-multitenancy-bom", version.ref = "micronaut-multitenancy" } 25 | micronaut-reactor = { module = "io.micronaut.reactor:micronaut-reactor-bom", version.ref = "micronaut-reactor" } 26 | micronaut-serde = { module = "io.micronaut.serde:micronaut-serde-bom", version.ref = "micronaut-serde" } 27 | micronaut-test-resources = { module = "io.micronaut.testresources:micronaut-test-resources-bom", version.ref = "micronaut-test-resources" } 28 | micronaut-validation = { module = "io.micronaut.validation:micronaut-validation-bom", version.ref = "micronaut-validation" } 29 | 30 | # Managed libraries 31 | managed-pulsar-client = { module = 'org.apache.pulsar:pulsar-client-original', version.ref= 'managed-pulsar-client' } 32 | 33 | # Other libraries 34 | conscrypt-openjdk = { module = "org.conscrypt:conscrypt-openjdk-uber", version.ref="conscrypt-openjdk" } 35 | protobuf-java = { module = 'com.google.protobuf:protobuf-java', version.ref = 'protobuf-java' } 36 | 37 | kotest-runner = { module = 'io.kotest:kotest-runner-junit5', version.ref = 'kotest-runner' } 38 | testcontainers-pulsar = { module = 'org.testcontainers:pulsar' } 39 | gradle-micronaut = { module = "io.micronaut.gradle:micronaut-gradle-plugin", version.ref = "micronaut-gradle-plugin" } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micronaut Pulsar 2 | 3 | [![Maven Central](https://img.shields.io/maven-central/v/io.micronaut.pulsar/micronaut-pulsar.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.micronaut.pulsar%22%20AND%20a:%22micronaut-pulsar%22) 4 | [![Build Status](https://github.com/micronaut-projects/micronaut-pulsar/workflows/Java%20CI/badge.svg)](https://github.com/micronaut-projects/micronaut-pulsar/actions) 5 | [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.micronaut.io/scans) 6 | 7 | This project includes integration between Apache Pulsar and Micronaut. 8 | ## Documentation 9 | 10 | See the [Documentation](https://micronaut-projects.github.io/micronaut-pulsar/latest/guide/) for more information. 11 | 12 | See the [Snapshot Documentation](https://micronaut-projects.github.io/micronaut-pulsar/snapshot/guide/) for the current development docs. 13 | 14 | ## Examples 15 | 16 | Examples can be found in the [examples](https://github.com/micronaut-projects/micronaut-pulsar/tree/master/examples) directory. 17 | 18 | ## Snapshots and Releases 19 | 20 | Snaphots are automatically published to [JFrog OSS](https://oss.jfrog.org/artifactory/oss-snapshot-local/) using [Github Actions](https://github.com/micronaut-projects/micronaut-pulsar/actions). 21 | 22 | See the documentation in the [Micronaut Docs](https://docs.micronaut.io/latest/guide/index.html#usingsnapshots) for how to configure your build to use snapshots. 23 | 24 | Releases are published to JCenter and Maven Central via [Github Actions](https://github.com/micronaut-projects/micronaut-pulsar/actions). 25 | 26 | Releases are completely automated. To perform a release use the following steps: 27 | 28 | * [Publish the draft release](https://github.com/micronaut-projects/micronaut-pulsar/releases). There should be already a draft release created, edit and publish it. The Git Tag should start with `v`. For example `v1.0.0`. 29 | * [Monitor the Workflow](https://github.com/micronaut-projects/micronaut-pulsar/actions?query=workflow%3ARelease) to check it passed successfully. 30 | * Celebrate! -------------------------------------------------------------------------------- /test-suite/test-pulsar-shared-module/src/test/resources/broker.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFpzCCA4+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwdjELMAkGA1UEBhMCVVMx 3 | EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCVBhbG8gQWx0bzESMBAGA1UE 4 | CgwJTWljcm9uYXV0MQ8wDQYDVQQLDAZQdWxzYXIxGTAXBgNVBAMMEG1pY3JvbmF1 5 | dC1wdWxzYXIwHhcNMjEwNDI1MjMzNzIxWhcNNDgwOTEwMjMzNzIxWjBiMQswCQYD 6 | VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJTWljcm9uYXV0 7 | MQ8wDQYDVQQLDAZQdWxzYXIxGTAXBgNVBAMMEG1pY3JvbmF1dC1wdWxzYXIwggEi 8 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9/auywhjDEZrQ1TRs+lB72eNu 9 | bD/84IOqThut8DmUQ6m1f3vdiEbpYQTaFPhQR/kL/3/wHcRQicPRRBXZB4YvVEQH 10 | IR3yfz1EW3FvjxCQ1VC2G4aIpoekiMBitBOpd+L9BatPsXh4bnZu0wFhjSKtFQzG 11 | +WZ6HPBDQ2j5AuJgmzbugpYx6Zv9rlYtGn/kQQJhah/+UI8mdl7B9hLNcwBDr7G1 12 | Lw1WvZH7k8bY1Lvhj4ZUhlMEi8Gs9QRJt659/ieLg6/LELi8XnJCv7VCkyyNYw00 13 | hfMwhql9/e/XNV1VjMOk8A5BxZZtSmVhDMwAToxvbEK6KZTpXbP+w8AFFYlpAgMB 14 | AAGjggFRMIIBTTAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgB 15 | hvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0G 16 | A1UdDgQWBBQQpFNj1RcfbdG6gJbOiN663at2DjCBswYDVR0jBIGrMIGogBQYt1SV 17 | ughAQBxVMeS91qs8+4oSOqF6pHgwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh 18 | bGlmb3JuaWExEjAQBgNVBAcMCVBhbG8gQWx0bzESMBAGA1UECgwJTWljcm9uYXV0 19 | MQ8wDQYDVQQLDAZQdWxzYXIxGTAXBgNVBAMMEG1pY3JvbmF1dC1wdWxzYXKCFGXQ 20 | FdBwh8ZlLwxIzxx06d43OOqAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr 21 | BgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAyPkL4/p0pgvIEv+d4bgcw16LrVCW 22 | qx8aszWt/F51DZO8DI52M7+EShWQUE0CMoumX8LrnDYNHVpsul3dU52yKTGn011D 23 | 0jGLSQD4PDZtdeZOVu7V5kym9oSAT3bTaGo2MWZKJlP/hp/vMbY8NfY4SF4wEh08 24 | AmW5s6Vf6ZU4A/Dhx8189hjrcWDtZVzicIhxx7LkzFLRVIPY9aBTsJeYYyYY7OsD 25 | uK7FyKYBTMT/O3S6q6hg7KgkssNsdq7PK4mRkPNh2GC92FzfXGcVc+YcJcjXhPP5 26 | 1RvAbsGO95/NdYA4WqiQpv53ntSxnruj4tVhUEUJ/OuVuEazPxt7BGdFUXzCE7Hp 27 | tim3FZD5JoikzaHPdbOGaYVo8QbQEjg0SJA8xYI0CqfncQcxIo7AnvsVtbpTcHAr 28 | T5IjEccDGzyzx8N+e9sAbwDm/5RpeszWLoKYlKoYWpw/SXua5GRxKR32sPHGQPLP 29 | SW+uqfgIrTEprHDMLb+HWp3/eKC0icSqadRYVBktQW9wT0HsrPAEy6hCkwLZXyJ0 30 | grulUz91swOBsKHYat5YOhlQrQNwjg7s0Qp07In5LaoQSAtlw5r7tVOoqL1UsJDd 31 | JHuVkW+shezzX6P1nkN+DUc/QpxiqblPClN7g1fdp4EnEd+2M3wNR3vdD8D0WEt+ 32 | 6xOSln3MMd4Fd8g= 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/processor/DefaultTopicResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.processor; 17 | 18 | import io.micronaut.core.annotation.Internal; 19 | import io.micronaut.messaging.exceptions.MessageListenerException; 20 | import io.micronaut.pulsar.config.PulsarClientConfiguration; 21 | import jakarta.inject.Singleton; 22 | 23 | /** 24 | * Resolve topic name to same value as input if tenant is hardcoded. Otherwise check for ${tenant} and default tenant 25 | * name specified in the configuration; if present resolve to that value; otherwise throw exception. 26 | * 27 | * @author Haris 28 | * @since 1.2.0 29 | */ 30 | @Internal 31 | @Singleton 32 | final class DefaultTopicResolver implements TopicResolver { 33 | 34 | private final String defaultTenant; 35 | 36 | DefaultTopicResolver(final PulsarClientConfiguration pulsarClientConfiguration) { 37 | defaultTenant = pulsarClientConfiguration.getDefaultTenant().orElse(null); 38 | } 39 | 40 | @Override 41 | public String resolve(final String topic) { 42 | if (TopicResolver.isDynamicTenantInTopic(topic)) { 43 | if (null == defaultTenant) { 44 | throw new MessageListenerException(String.format( 45 | "Consumer specified dynamic tenant name in topic %s but no default tenant name set in the configuration", 46 | topic)); 47 | } 48 | return TopicResolver.replaceTenantInTopic(topic, defaultTenant); 49 | } 50 | return topic; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for reporting an issue, please review the task list below before submitting the issue. Your issue report will be closed if the issue is incomplete and the below tasks not completed. 8 | 9 | NOTE: If you are unsure about something and the issue is more of a question a better place to ask questions is on Github Discussions :arrow_up:, [Stack Overflow](https://stackoverflow.com/tags/micronaut) or [Gitter](https://gitter.im/micronautfw/). 10 | - type: textarea 11 | attributes: 12 | label: Expected Behavior 13 | description: A concise description of what you expected to happen. 14 | placeholder: Tell us what should happen 15 | validations: 16 | required: false 17 | - type: textarea 18 | attributes: 19 | label: Actual Behaviour 20 | description: A concise description of what you're experiencing. 21 | placeholder: Tell us what happens instead 22 | validations: 23 | required: false 24 | - type: textarea 25 | attributes: 26 | label: Steps To Reproduce 27 | description: Steps to reproduce the behavior. 28 | placeholder: | 29 | 1. In this environment... 30 | 2. With this config... 31 | 3. Run '...' 32 | 4. See error... 33 | validations: 34 | required: false 35 | - type: textarea 36 | attributes: 37 | label: Environment Information 38 | description: Environment information where the problem occurs. 39 | placeholder: | 40 | - Operating System: 41 | - JDK Version: 42 | validations: 43 | required: false 44 | - type: input 45 | id: example 46 | attributes: 47 | label: Example Application 48 | description: Example application link. 49 | placeholder: | 50 | Link to GitHub repository with an example that reproduces the issue 51 | validations: 52 | required: false 53 | - type: input 54 | id: version 55 | attributes: 56 | label: Version 57 | description: Micronaut version 58 | validations: 59 | required: true 60 | 61 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/src/test/groovy/io/micronaut/pulsar/dynamic/FakeController.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.dynamic 17 | 18 | import io.micronaut.context.annotation.Requires 19 | import io.micronaut.context.event.ApplicationEventPublisher 20 | import io.micronaut.http.annotation.Body 21 | import io.micronaut.http.annotation.Controller 22 | import io.micronaut.http.annotation.Post 23 | import io.micronaut.multitenancy.tenantresolver.TenantResolver 24 | import io.micronaut.pulsar.events.PulsarTenantDiscoveredEvent 25 | 26 | @Requires(property = 'spec.name', value = 'DynamicTenantTopicSpec') 27 | @Controller("/") 28 | class FakeController { 29 | private final ProducerDynamicTenantTopicTester producer 30 | final TenantResolver tenantResolver 31 | final ApplicationEventPublisher tenantPublisher 32 | 33 | FakeController(ProducerDynamicTenantTopicTester producer, 34 | TenantResolver tenantResolver, 35 | ApplicationEventPublisher tenantPublisher) { 36 | this.producer = producer 37 | this.tenantResolver = tenantResolver 38 | this.tenantPublisher = tenantPublisher 39 | } 40 | 41 | @Post("/messages") 42 | String send(@Body String message) { 43 | return producer.send(message).toString() 44 | } 45 | 46 | @Post("/tenant") 47 | String addTenant(@Body String tenant) { 48 | tenantPublisher.publishEvent(new PulsarTenantDiscoveredEvent(tenant)) 49 | return tenant.toString() 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-shared-module/src/test/resources/ca.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF3TCCA8WgAwIBAgIUZdAV0HCHxmUvDEjPHHTp3jc46oAwDQYJKoZIhvcNAQEL 3 | BQAwdjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcM 4 | CVBhbG8gQWx0bzESMBAGA1UECgwJTWljcm9uYXV0MQ8wDQYDVQQLDAZQdWxzYXIx 5 | GTAXBgNVBAMMEG1pY3JvbmF1dC1wdWxzYXIwHhcNMjEwNDI1MjMzMzQwWhcNNDEw 6 | NDIwMjMzMzQwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTES 7 | MBAGA1UEBwwJUGFsbyBBbHRvMRIwEAYDVQQKDAlNaWNyb25hdXQxDzANBgNVBAsM 8 | BlB1bHNhcjEZMBcGA1UEAwwQbWljcm9uYXV0LXB1bHNhcjCCAiIwDQYJKoZIhvcN 9 | AQEBBQADggIPADCCAgoCggIBAMpDhq7Lr7ar1X0q07uJE85xswOxOO9535FADDPl 10 | HKO0x5btGKjvjWV94h/f5a6FHMcq2+W8dzD72Ru8bIuqAb8nuheenwU8yH2T4pgW 11 | +ry3W3ad87PisB5CZW2w6Y5AGfqOm1OtReBhXZ/Rxs7Pa7W8zek6shns+CGva6T7 12 | YYEomHI17N3ZHDxTTiJnigE+CiZ4q2NKFtMcUI/y3p2GsBPC5njFfTHAj1mLFkbO 13 | zGu4E9xE2BUEz78pxc/RTCDg/TdLwoAV9cK/6W3QFeOPd2UORpPCzEyPfYlMjSKb 14 | DdmloK+LGJfTiJMSwJ1JBG3jcftqWR91bzKtDVWl3mpYaHtg+FupdQ7Oi68Bq3Hd 15 | 8vXeO1HP2TEsSBjSdUy7m64MP7xhF2zlMkDFa2Q4GTGUkTM6Jmq8887kakvneAgB 16 | D0NhOYrpGHasd/XLyaqOi3xgO9Lg6pN0cxW6tlpw3s2vHYiJOy82K0B/6SOq20mL 17 | YwaxI4vWeWOpuPlfrGe6YwopwqUv5On3Ei71tFESxeSLiGQoUF4hW9tt4qN/0tpH 18 | /yA305C7qExB/oO3LW7HnwiMkWIi4WByFzsfckKhQPDiDFF/7hsQcfyOOvpMP6H8 19 | /Qx40OHWjhkoxqNXVzG1EJ75UzEK04SlnsTBaw6/e1zk9zfVEg/wm6cIrlYboBw9 20 | WsQHAgMBAAGjYzBhMB0GA1UdDgQWBBQYt1SVughAQBxVMeS91qs8+4oSOjAfBgNV 21 | HSMEGDAWgBQYt1SVughAQBxVMeS91qs8+4oSOjAPBgNVHRMBAf8EBTADAQH/MA4G 22 | A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAMIEEYZJRZEFDwl4bfZY+ 23 | a1Lrv/i3A24sVSWNBGWO2Tf5f0MskC9S0TlPLYW3h+mDRRRcxA02+SzDbW0ogVDN 24 | H0+95T3biScZSsCTtEV/15GdREXIUSaKb/oFBIjqPUZBet3XtMzLUOG0dVhBJgrR 25 | ekkw7iqA6gYveXSem0IlQHeeEZlPbTXSa8HWlfIZyVPL3vTleJHSB64DyCSn1RjP 26 | MmfKKDnWcZkemNBdL9744pcl7rIZ3SSKC3x4zCyAZnlTSQfs7vIp/2g71ei5aKd4 27 | KkhUxjp8XDsRtX5iij/Nz01z+6UBfpP6KCdUwrIGnfTQazoCuq0tMdohrbSoXYVL 28 | coViS+z2PF5V1uT66dEENvfaXaz5hMOf4v/DindySVaR80F2OhS4+xEhiCnPNFuT 29 | LnSnpZ+jnyLE5MvKO0iKRpjeA6K1O0iMX+2LWJAD9+TbOuSWYlXF55C3pPqf6Src 30 | TW6swgNQ6avH412wVw2d7bUcl32PCWt1fwVtXNC86GLNLFOXa18zZFShNzhPpjj0 31 | LTR3TKsgZe47GqgiDds8V/mwMr1XHMBJGun9ggFrfjOjEmvcR6hzvP+miR5iIbaY 32 | AcGX/P4MLMNBj6J06Fx4QiIh/yKmem0K6w6wr7/1BU7HZtXbohWPpiV2DEVhCzyE 33 | qy2EDZ2K1Ked9S80c8Z+qXI= 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /src/main/docs/guide/authentication.adoc: -------------------------------------------------------------------------------- 1 | === JWT 2 | 3 | To configure JWT authentication it's sufficient to specify a JWT in `application.yml` under `pulsar.authentication-jwt`, e.g.: 4 | 5 | [configuration] 6 | ---- 7 | pulsar: 8 | authentication-jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX... 9 | ---- 10 | 11 | `io.micronaut.configuration.pulsar.config.AbstractPulsarConfiguration` is left public in case you want to implement more options yourself. 12 | 13 | It's planned to support specifying a JWT path, either an HTTP endpoint or URL to retrieve a fresh JWT. 14 | 15 | === OAuth2 16 | 17 | To use OAuth2 authentication, configure three parameters like in the example below to connect to a Pulsar service that uses OAuth2: 18 | 19 | [configuration] 20 | ---- 21 | pulsar: 22 | oauth-issuer-url: https://some-sso.io/... 23 | oauth-credentials-url: file:///path/to/file.json 24 | audience: localhost:6650 25 | ---- 26 | 27 | The issuer URL is the URL to the OAuth2 server generating tokens and such. 28 | 29 | Parameter `oauth-credentials-url` is used for reading a file containing necessary configuration for authenticating to the OAuth2 provider as a client app. The file should contain everything defined in Pulsar documentation, and type is limited to `client_credentials`. Below is an example of what the file should look like. 30 | 31 | [source,json] 32 | ---- 33 | { 34 | "type": "client_credentials", 35 | "client_id": "pulsar", 36 | "client_secret": "1234-abcd-5678-9011-ab56ac4564sa56", 37 | "issuer_url": "https://my-oauth2-server.com/auth/realms/pulsar-realm" 38 | } 39 | ---- 40 | 41 | The example shows something similar that could be set when using KeyCloak or a similar SSO. Audience must be set, but Pulsar service can be configured to ignore this value, and in such case you might put any text with one or more characters. For more details, see the https://pulsar.apache.org/docs/en/security-oauth2/[Apache Pulsar documentation]. Consult Java client to understand more about limitations for `client_credentials`. 42 | 43 | The Pulsar library takes care of the OAuth2 JWT refreshing and such. The Pulsar server must be configured to use OAuth2 44 | and _role_ attribute in its configuration files must be specified for Pulsar to be able to detect which kind of _"user"_ is requesting data. 45 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/SchemaResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas; 17 | 18 | import org.apache.pulsar.client.api.Schema; 19 | 20 | /* 21 | * Copyright 2017-2022 original authors 22 | * 23 | * Licensed under the Apache License, Version 2.0 (the "License"); 24 | * you may not use this file except in compliance with the License. 25 | * You may obtain a copy of the License at 26 | * 27 | * https://www.apache.org/licenses/LICENSE-2.0 28 | * 29 | * Unless required by applicable law or agreed to in writing, software 30 | * distributed under the License is distributed on an "AS IS" BASIS, 31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | * See the License for the specific language governing permissions and 33 | * limitations under the License. 34 | */ 35 | 36 | /*** 37 | * SchemaResolver represents basic bean that will handle serde operations for a given type. Beans should be named and 38 | * each should represent schema which Apache Pulsar should use as transport type. For example JSON is wrapped in AVRO 39 | * and Protobuf is natively support but thus 2 SchemaResolvers should exist. To avoid dependency checking default type 40 | * names are listed here. 41 | * 42 | * Note: Primitive types are passed down to official Pulsar Java library thus no SchemaResolver names are listed here 43 | * for them. 44 | */ 45 | public interface SchemaResolver { 46 | String JSON_SCHEMA_NAME = "JSON_SCHEMA_RESOLVER"; 47 | String PROTOBUF_SCHEMA_NAME = "PROTOBUF_SCHEMA_RESOLVER"; 48 | String AVRO_SCHEMA_NAME = "AVRO_SCHEMA_RESOLVER"; 49 | 50 | Schema forArgument(Class pojo); 51 | } 52 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/src/test/groovy/io/micronaut/pulsar/PulsarConfigurationSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar 17 | 18 | import io.micronaut.context.ApplicationContext 19 | import io.micronaut.context.env.Environment 20 | import io.micronaut.multitenancy.tenantresolver.TenantResolver 21 | import io.micronaut.pulsar.processor.TenantNameResolver 22 | import io.micronaut.pulsar.processor.TopicResolver 23 | import io.micronaut.pulsar.shared.PulsarTls 24 | import spock.lang.AutoCleanup 25 | import spock.lang.Shared 26 | import spock.lang.Specification 27 | import spock.lang.Stepwise 28 | 29 | @Stepwise 30 | class PulsarConfigurationSpec extends Specification { 31 | 32 | @Shared 33 | @AutoCleanup 34 | ApplicationContext context 35 | 36 | public static final TENANT_NAME = 'public' 37 | 38 | void setupSpec() { 39 | context = ApplicationContext.run( 40 | ['pulsar.service-url' : PulsarTls.pulsarBrokerUrl, 41 | 'pulsar.shutdown-on-subscriber-error' : true, 42 | 'spec.name' : getClass().simpleName, 43 | 'micronaut.multitenancy.tenantresolver.fixed.enabled' : true, 44 | 'micronaut.multitenancy.tenantresolver.fixed.tenant-id': PulsarConfigurationSpec.TENANT_NAME], 45 | Environment.TEST 46 | ) 47 | } 48 | 49 | void "test load configuration"() { 50 | expect: 51 | context.isRunning() 52 | context.containsBean(TenantResolver) 53 | context.containsBean(TopicResolver) 54 | context.containsBean(TenantNameResolver) 55 | context.getBean(TenantNameResolver).getCurrentTenantName() == PulsarConfigurationSpec.TENANT_NAME 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/src/test/groovy/io/micronaut/pulsar/PulsarAwareTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar 17 | 18 | import io.micronaut.context.ApplicationContext 19 | import io.micronaut.context.env.Environment 20 | import io.micronaut.pulsar.shared.PulsarTls 21 | import spock.lang.AutoCleanup 22 | import spock.lang.Shared 23 | import spock.lang.Specification 24 | 25 | abstract class PulsarAwareTest extends Specification { 26 | 27 | @Shared 28 | @AutoCleanup 29 | ApplicationContext context 30 | 31 | static { 32 | PulsarTls.createTopic(PulsarConsumerSpec.PULSAR_REGEX_TEST_TOPIC) 33 | PulsarTls.createTopic(PulsarConsumerSpec.PULSAR_STATIC_TOPIC_TEST) 34 | PulsarTls.createTopic(PulsarProducersSpec.PULSAR_PRODUCER_TEST_TOPIC) 35 | PulsarTls.createTopic(PulsarReaderSpec.PULSAR_READER_TEST_TOPIC_INJECTABLE) 36 | PulsarTls.createTopic(PulsarReaderSpec.PULSAR_READER_TEST_TOPIC_METHOD) 37 | PulsarTls.createTopic(PulsarReaderSpec.PULSAR_READER_TEST_TOPIC_METHOD_WRAPPED) 38 | PulsarTls.createTopic(PulsarReaderSpec.PULSAR_READER_TEST_TOPIC_METHOD_ASYNC) 39 | PulsarTls.createTopic(PulsarReaderSpec.PULSAR_READER_TEST_TOPIC_METHOD_ASYNC_WRAPPED) 40 | PulsarTls.createTopic(PulsarSchemaSpec.PULSAR_JSON_TOPIC) 41 | PulsarTls.createTopic(PulsarSchemaSpec.PULSAR_PROTOBUF_TOPIC) 42 | } 43 | 44 | void setupSpec() { 45 | context = ApplicationContext.run( 46 | ['pulsar.service-url' : PulsarTls.pulsarBrokerUrl, 47 | 'pulsar.shutdown-on-subscriber-error': true, 48 | 'pulsar.testSub.testConsumerName' : PulsarConsumerSpec.PULSAR_CONSUMER_NAME_PROPERTY_VALUE, 49 | 'spec.name' : getClass().simpleName], 50 | Environment.TEST 51 | ) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/PulsarSubscription.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.annotation; 17 | 18 | import io.micronaut.messaging.annotation.MessageListener; 19 | import org.apache.pulsar.client.api.SubscriptionType; 20 | 21 | import java.lang.annotation.Documented; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.Target; 24 | 25 | import static java.lang.annotation.ElementType.TYPE; 26 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 27 | import static org.apache.pulsar.client.api.SubscriptionType.Exclusive; 28 | 29 | /** 30 | * Mark a class that contains Pulsar consumers. Each method in class should be 31 | * isolated consumer. However, Pulsar provides multiple consumers via single 32 | * subscription if they are set to Failover, Share, or such. 33 | * 34 | * @author Haris Secic 35 | * @since 1.0 36 | */ 37 | @Documented 38 | @Retention(RUNTIME) 39 | @Target(TYPE) 40 | @MessageListener 41 | public @interface PulsarSubscription { 42 | 43 | /** 44 | * If not set, UUID will be generated as subscription name to avoid 45 | * collisions if consumer type is Exclusive. 46 | * 47 | * @return Subscription name 48 | * @see org.apache.pulsar.client.api.ConsumerBuilder#subscriptionType 49 | */ 50 | String subscriptionName() default ""; 51 | 52 | /** 53 | * By default {@code Exclusive}. 54 | * 55 | * @return Type of consumer subscription 56 | * @see org.apache.pulsar.client.api.ConsumerBuilder#subscriptionType 57 | */ 58 | SubscriptionType subscriptionType() default Exclusive; 59 | 60 | /** 61 | * By default it will use PulsarConsumer builder default values. 62 | * 63 | * @return Maximum amount of time allowed to pass for message to be 64 | * acknowledged or else redelivery happens. 65 | * @see org.apache.pulsar.client.api.ConsumerBuilder#acknowledgmentGroupTime 66 | */ 67 | String ackGroupTimeout() default ""; 68 | } 69 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/protobuf/ProtobufSchema.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.protobuf; 17 | 18 | import io.micronaut.core.type.Argument; 19 | import io.micronaut.protobuf.codec.ProtobufferCodec; 20 | import org.apache.pulsar.client.api.schema.SchemaDefinition; 21 | import org.apache.pulsar.client.impl.schema.AbstractStructSchema; 22 | import org.apache.pulsar.client.impl.schema.SchemaDefinitionBuilderImpl; 23 | import org.apache.pulsar.client.impl.schema.util.SchemaUtil; 24 | import org.apache.pulsar.common.schema.SchemaInfo; 25 | import org.apache.pulsar.common.schema.SchemaType; 26 | 27 | import java.util.Map; 28 | import java.util.concurrent.ConcurrentHashMap; 29 | 30 | /** 31 | * JSON Schema to allow using {@link ProtobufferCodec} from Micronaut. 32 | * 33 | * @param POJO type to send and receive via Pulsar. 34 | * @author Haris Secic 35 | * @since 1.0 36 | */ 37 | public final class ProtobufSchema extends AbstractStructSchema { 38 | 39 | private static final Map> SCHEMAS = new ConcurrentHashMap<>(10); 40 | 41 | public ProtobufSchema(final SchemaInfo schemaInfo, final ProtobufReader reader, final ProtobufWriter writer) { 42 | super(schemaInfo); 43 | this.setReader(reader); 44 | this.setWriter(writer); 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | public static ProtobufSchema of(final Class type, final ProtobufferCodec codec) { 49 | return (ProtobufSchema) SCHEMAS.computeIfAbsent(type.hashCode(), x -> { 50 | final ProtobufWriter writer = new ProtobufWriter<>(codec); 51 | final ProtobufReader reader = new ProtobufReader<>(codec, Argument.of(type)); 52 | final SchemaDefinition schemaDefinition = new SchemaDefinitionBuilderImpl() 53 | .withPojo(type) 54 | .withSchemaWriter(writer) 55 | .withSchemaReader(reader) 56 | .build(); 57 | return new ProtobufSchema<>(SchemaUtil.parseSchemaInfo(schemaDefinition, SchemaType.PROTOBUF), reader, writer); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pulsar-multitenant/src/main/java/io/micronaut/pulsar/processor/MultiTenantTopicResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.processor; 17 | 18 | import io.micronaut.context.annotation.Replaces; 19 | import io.micronaut.core.annotation.Internal; 20 | import io.micronaut.messaging.exceptions.MessageListenerException; 21 | import io.micronaut.multitenancy.exceptions.TenantNotFoundException; 22 | import io.micronaut.pulsar.config.AbstractPulsarConfiguration; 23 | import jakarta.inject.Singleton; 24 | 25 | /** 26 | * Topic resolver for multi tenant scenarios. Replaces ${tenant} with the actual Apache Pulsar tenant name. 27 | * 28 | * @author Haris 29 | * @since 1.2.0 30 | */ 31 | @Singleton 32 | @Replaces(bean = TopicResolver.class) 33 | @Internal 34 | final class MultiTenantTopicResolver implements TopicResolver { 35 | 36 | private static final String FORMAT_ID = "%s-%s"; 37 | private final TenantNameResolver tenantNameResolver; 38 | 39 | public MultiTenantTopicResolver(final TenantNameResolver tenantNameResolver) { 40 | this.tenantNameResolver = tenantNameResolver; 41 | } 42 | 43 | @Override 44 | public String resolve(final String topic) throws TenantNotFoundException, MessageListenerException { 45 | if (!TopicResolver.isDynamicTenantInTopic(topic)) { 46 | return topic; 47 | } 48 | 49 | final String tenantName = tenantNameResolver.getCurrentTenantName(); 50 | 51 | if (!TenantNameResolver.isValidTenantName(tenantName)) { 52 | throw new MessageListenerException(String.format( 53 | "Invalid value for topic: %s while resolving tenant: %s. Tenant name does not match pattern: %s", 54 | topic, 55 | tenantName, 56 | AbstractPulsarConfiguration.TENANT_NAME_VALIDATOR)); 57 | } 58 | 59 | return TopicResolver.replaceTenantInTopic(topic, tenantName); 60 | } 61 | 62 | @Override 63 | public String generateIdFromMessagingClientName(final String name, final TopicResolved topicResolved) { 64 | if (topicResolved.isDynamicTenant()) { 65 | return String.format(FORMAT_ID, tenantNameResolver.getCurrentTenantName(), name); 66 | } 67 | return name; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/src/test/groovy/io/micronaut/pulsar/PulsarConfigurationTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar 17 | 18 | import org.apache.pulsar.common.schema.SchemaType 19 | import io.micronaut.json.JsonMapper 20 | import io.micronaut.protobuf.codec.ProtobufferCodec 21 | import io.micronaut.pulsar.config.PulsarClientConfiguration 22 | import io.micronaut.pulsar.intercept.PulsarProducerAdvice 23 | import io.micronaut.pulsar.intercept.PulsarReaderAdvice 24 | import io.micronaut.pulsar.processor.PulsarConsumerProcessor 25 | import io.micronaut.pulsar.schemas.avro.AvroSchemaResolver 26 | import io.micronaut.pulsar.schemas.json.JsonSchema 27 | import io.micronaut.pulsar.schemas.json.JsonSchemaResolver 28 | import io.micronaut.pulsar.schemas.protobuf.ProtobufSchema 29 | import io.micronaut.pulsar.schemas.protobuf.ProtobufSchemaResolver 30 | import io.micronaut.pulsar.shared.PulsarTls 31 | import org.apache.pulsar.client.api.PulsarClient 32 | import spock.lang.Stepwise 33 | 34 | @Stepwise 35 | class PulsarConfigurationTest extends PulsarAwareTest { 36 | 37 | void "test load configuration"() { 38 | expect: 39 | context.isRunning() 40 | context.containsBean(PulsarClientConfiguration) 41 | context.containsBean(PulsarClient) 42 | context.containsBean(PulsarConsumerProcessor) 43 | context.containsBean(PulsarProducerAdvice) 44 | context.containsBean(PulsarReaderAdvice) 45 | context.containsBean(JsonSchemaResolver.class) 46 | context.containsBean(AvroSchemaResolver.class) 47 | context.containsBean(ProtobufferCodec.class) 48 | context.containsBean(ProtobufSchemaResolver.class) 49 | PulsarTls.pulsarBrokerUrl == context.getBean(PulsarClientConfiguration).serviceUrl 50 | } 51 | 52 | void "test schema resolvers"() { 53 | given: 54 | def jsonMapper = context.getBean(JsonMapper.class) 55 | def protoCodec = context.getBean(ProtobufferCodec.class) 56 | 57 | when: 58 | def jsonSchema = JsonSchema.of(JsonJavaClass.class, jsonMapper) 59 | def protoSchema = ProtobufSchema.of(ProtoMessages.ProtoMessage.class, protoCodec) 60 | 61 | then: 62 | jsonSchema.schemaInfo.getType() == SchemaType.JSON 63 | protoSchema.schemaInfo.getType() == SchemaType.PROTOBUF 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/graalvm-latest.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Do not edit this file directly. Instead, go to: 2 | # 3 | # https://github.com/micronaut-projects/micronaut-project-template/tree/master/.github/workflows 4 | # 5 | # and edit them there. Note that it will be sync'ed to all the Micronaut repos 6 | name: GraalVM Latest CI 7 | on: 8 | push: 9 | branches: 10 | - master 11 | - '[0-9]+.[0-9]+.x' 12 | pull_request: 13 | branches: 14 | - master 15 | - '[0-9]+.[0-9]+.x' 16 | jobs: 17 | build_matrix: 18 | if: github.repository != 'micronaut-projects/micronaut-project-template' 19 | runs-on: ubuntu-latest 20 | env: 21 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 22 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 23 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 24 | outputs: 25 | matrix: ${{ steps.build-matrix.outputs.matrix }} 26 | steps: 27 | - uses: actions/checkout@v6 28 | - name: Build Matrix 29 | uses: micronaut-projects/github-actions/graalvm/build-matrix@master 30 | id: build-matrix 31 | with: 32 | java-version: '21' 33 | build: 34 | needs: build_matrix 35 | if: github.repository != 'micronaut-projects/micronaut-project-template' 36 | runs-on: ubuntu-latest 37 | strategy: 38 | max-parallel: 6 39 | matrix: 40 | java: ['21'] 41 | native_test_task: ${{ fromJson(needs.build_matrix.outputs.matrix).native_test_task }} 42 | env: 43 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 44 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 45 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 46 | steps: 47 | - name: Remove system JDKs 48 | run: | 49 | sudo rm -rf /usr/lib/jvm/* 50 | unset JAVA_HOME 51 | export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/usr/lib/jvm' | paste -sd:) 52 | - uses: actions/checkout@v6 53 | - name: Pre-Build Steps 54 | uses: micronaut-projects/github-actions/graalvm/pre-build@master 55 | id: pre-build 56 | with: 57 | distribution: 'graalvm' 58 | gradle-java: '21' 59 | java: ${{ matrix.java }} 60 | nativeTestTask: ${{ matrix.native_test_task }} 61 | - name: Build Steps 62 | uses: micronaut-projects/github-actions/graalvm/build@master 63 | id: build 64 | env: 65 | GH_TOKEN_PUBLIC_REPOS_READONLY: ${{ secrets.GH_TOKEN_PUBLIC_REPOS_READONLY }} 66 | GH_USERNAME: ${{ secrets.GH_USERNAME }} 67 | GRAALVM_QUICK_BUILD: true 68 | with: 69 | nativeTestTask: ${{ matrix.native_test_task }} 70 | - name: Post-Build Steps 71 | uses: micronaut-projects/github-actions/graalvm/post-build@master 72 | id: post-build 73 | with: 74 | java: ${{ matrix.java }} 75 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/config/AbstractPulsarConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.config; 17 | 18 | import org.jspecify.annotations.NonNull; 19 | import org.apache.pulsar.client.api.Authentication; 20 | import org.apache.pulsar.client.impl.auth.AuthenticationDisabled; 21 | 22 | import java.util.Properties; 23 | 24 | /** 25 | * Base template class for configuring pulsar. 26 | * 27 | * @param key 28 | * @param value 29 | * @author Haris Secic 30 | * @since 1.0 31 | */ 32 | public abstract class AbstractPulsarConfiguration { 33 | 34 | /** 35 | * The default Apache Pulsar messaging port. 36 | */ 37 | public static final int DEFAULT_PULSAR_MESSAGING_PORT = 6650; 38 | 39 | /** 40 | * The default prefix used for Pulsar configuration. 41 | */ 42 | public static final String PREFIX = "pulsar"; 43 | 44 | /** 45 | * The default server hostname or IP address. 46 | */ 47 | public static final String DEFAULT_SERVER_HOST_ADDRESS = "localhost"; 48 | 49 | /** 50 | * The default bootstrap server address for messaging. 51 | */ 52 | public static final String DEFAULT_BOOTSTRAP_SERVER = "pulsar://" + DEFAULT_SERVER_HOST_ADDRESS + ":" + DEFAULT_PULSAR_MESSAGING_PORT; 53 | 54 | /** 55 | * By default Pulsar doesn't have any authentication. 56 | */ 57 | public static final Authentication DEFAULT_PULSAR_AUTHENTICATION = new AuthenticationDisabled(); 58 | 59 | /** 60 | * Regex for validating topic name. 61 | */ 62 | public static final String TOPIC_NAME_VALIDATOR = "((non-)?persistent://)?((\\w+(-|\\w+)*\\w)|(\\$\\{tenant\\}))/(\\w+(-|\\w+)*\\w)/(\\w+([-.]\\w+)*)"; 63 | 64 | /** 65 | * Regex for validating topic pattern. 66 | */ 67 | public static final String TOPIC_NAME_PATTERN_VALIDATOR = "((non-)?persistent://)?((\\w+(-|\\w+)*\\w)|(\\$\\{tenant\\}))\\/(\\w+(-?\\w+)?/).+"; 68 | 69 | public static final String TENANT_NAME_VALIDATOR = "\\w+(-|\\w+)*\\w"; 70 | 71 | private final Properties config; 72 | 73 | protected AbstractPulsarConfiguration(Properties config) { 74 | this.config = config; 75 | } 76 | 77 | /** 78 | * @return The Pulsar configuration 79 | */ 80 | @NonNull 81 | public Properties getConfig() { 82 | return config == null ? new Properties() : config; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/json/JsonSchema.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.json; 17 | 18 | import io.micronaut.json.JsonMapper; 19 | import org.apache.pulsar.client.api.schema.SchemaDefinition; 20 | import org.apache.pulsar.client.api.schema.SchemaReader; 21 | import org.apache.pulsar.client.api.schema.SchemaWriter; 22 | import org.apache.pulsar.client.impl.schema.AvroBaseStructSchema; 23 | import org.apache.pulsar.client.impl.schema.SchemaDefinitionBuilderImpl; 24 | import org.apache.pulsar.client.impl.schema.util.SchemaUtil; 25 | import org.apache.pulsar.common.schema.SchemaInfo; 26 | import org.apache.pulsar.common.schema.SchemaType; 27 | 28 | import java.util.Map; 29 | import java.util.concurrent.ConcurrentHashMap; 30 | 31 | /** 32 | * JSON Schema to allow using {@link JsonMapper} from Micronaut instead of shaded one in Pulsar library. 33 | * 34 | * @param POJO type to send and receive via Pulsar. 35 | * @author Haris Secic 36 | * @since 1.0 37 | */ 38 | public class JsonSchema extends AvroBaseStructSchema { 39 | 40 | private static final Map> SCHEMAS = new ConcurrentHashMap<>(10); 41 | 42 | public JsonSchema(SchemaInfo schemaInfo, SchemaReader reader, SchemaWriter writer) { 43 | super(schemaInfo); 44 | this.setWriter(writer); 45 | this.setReader(reader); 46 | } 47 | 48 | /** 49 | * @param pojo The pojo class to map 50 | * @param jsonMapper The json mapper to use for mapping 51 | * @param The pojo class to map 52 | * @return The parsed json schema 53 | * @since 1.1.0 54 | */ 55 | @SuppressWarnings("unchecked") 56 | public static JsonSchema of(Class pojo, JsonMapper jsonMapper) { 57 | return (JsonSchema) SCHEMAS.computeIfAbsent(pojo.hashCode(), x -> { 58 | final SchemaReader reader = new JsonReader<>(jsonMapper, pojo); 59 | final SchemaWriter writer = new JsonWriter<>(jsonMapper); 60 | final SchemaDefinition schemaDefinition = new SchemaDefinitionBuilderImpl().withPojo(pojo) 61 | .withSchemaReader(reader) 62 | .withSchemaWriter(writer) 63 | .build(); 64 | return new JsonSchema<>(SchemaUtil.parseSchemaInfo(schemaDefinition, SchemaType.JSON), reader, writer); 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.clinerules/docs.md: -------------------------------------------------------------------------------- 1 | ## Brief overview 2 | - Documentation for the Micronaut modules is primarily written in Asciidoc format, focusing on user guides, API references, and integration examples. The documentation emphasizes clarity, completeness, and practical examples to help developers integrate Micronaut modules effectively. Insights from the codebase show a focus on modular documentation aligned with subprojects, including setup instructions, usage examples, and troubleshooting tips. 3 | - All the files are within `src/main/docs/guide`. In that directory, there is a `toc.yml` file that is used to generate the table of contents and decide which `.adoc` files are to be included. 4 | 5 | ## Development workflow 6 | - Write documentation in Asciidoc: Place source files in the appropriate `src/main/docs` directory. 7 | - Build and assemble the documentation guide: Use `./gradlew docs` from the root directory to generate HTML documentation. Since the output of this task may be huge, ignore the output and check the last process exit code to tell if it works. Otherwise, ask the user. If it works, verify the output for formatting and content accuracy. 8 | - Once assembled, the guide will be at `build/docs/`. 9 | - Include examples: Create and reference code examples from the doc-examples/ directory, ensuring they are testable and up-to-date with the latest service versions. 10 | - Test documentation: Run builds regularly and check for broken links or outdated information. Integrate doc checks into CI pipelines using Gradle tasks. 11 | - Review and update: Conduct peer reviews for new documentation or changes, ensuring alignment with coding standards and project updates. 12 | 13 | ## Documentation best practices 14 | - Follow Asciidoc conventions: Use consistent headings, lists, code blocks, and admonitions (e.g., NOTE, TIP, WARNING) for better readability. 15 | - Provide comprehensive coverage: Include installation instructions, configuration details, usage examples, error handling, and performance tips for each service. 16 | - Use practical examples: Incorporate runnable code snippets from doc-examples/ to demonstrate real-world usage, with clear explanations and expected outputs. 17 | - Ensure accessibility: Use descriptive alt text for images, maintain logical structure, and avoid jargon without explanations. 18 | - Version control: Document version-specific changes and maintain backward compatibility notes. 19 | - Security and best practices: Highlight secure usage patterns, such as proper authentication and data handling. 20 | 21 | ## Project context 22 | - Focus on Micronaut-specific integration that this project is providing, emphasizing GraalVM compatibility, annotation-driven configurations, and modular design. 23 | - Prioritize user-centric content: Guides should facilitate quick starts, advanced customizations, and troubleshooting for developers building Micronaut applications. 24 | - Align with coding guidelines: Documentation should complement code by explaining architectural decisions, such as the use of factories, interceptors, and annotation processors. 25 | -------------------------------------------------------------------------------- /.github/instructions/docs.instructions.md: -------------------------------------------------------------------------------- 1 | ## Brief overview 2 | - Documentation for the Micronaut modules is primarily written in Asciidoc format, focusing on user guides, API references, and integration examples. The documentation emphasizes clarity, completeness, and practical examples to help developers integrate Micronaut modules effectively. Insights from the codebase show a focus on modular documentation aligned with subprojects, including setup instructions, usage examples, and troubleshooting tips. 3 | - All the files are within `src/main/docs/guide`. In that directory, there is a `toc.yml` file that is used to generate the table of contents and decide which `.adoc` files are to be included. 4 | 5 | ## Development workflow 6 | - Write documentation in Asciidoc: Place source files in the appropriate `src/main/docs` directory. 7 | - Build and assemble the documentation guide: Use `./gradlew docs` from the root directory to generate HTML documentation. Since the output of this task may be huge, ignore the output and check the last process exit code to tell if it works. Otherwise, ask the user. If it works, verify the output for formatting and content accuracy. 8 | - Once assembled, the guide will be at `build/docs/`. 9 | - Include examples: Create and reference code examples from the doc-examples/ directory, ensuring they are testable and up-to-date with the latest service versions. 10 | - Test documentation: Run builds regularly and check for broken links or outdated information. Integrate doc checks into CI pipelines using Gradle tasks. 11 | - Review and update: Conduct peer reviews for new documentation or changes, ensuring alignment with coding standards and project updates. 12 | 13 | ## Documentation best practices 14 | - Follow Asciidoc conventions: Use consistent headings, lists, code blocks, and admonitions (e.g., NOTE, TIP, WARNING) for better readability. 15 | - Provide comprehensive coverage: Include installation instructions, configuration details, usage examples, error handling, and performance tips for each service. 16 | - Use practical examples: Incorporate runnable code snippets from doc-examples/ to demonstrate real-world usage, with clear explanations and expected outputs. 17 | - Ensure accessibility: Use descriptive alt text for images, maintain logical structure, and avoid jargon without explanations. 18 | - Version control: Document version-specific changes and maintain backward compatibility notes. 19 | - Security and best practices: Highlight secure usage patterns, such as proper authentication and data handling. 20 | 21 | ## Project context 22 | - Focus on Micronaut-specific integration that this project is providing, emphasizing GraalVM compatibility, annotation-driven configurations, and modular design. 23 | - Prioritize user-centric content: Guides should facilitate quick starts, advanced customizations, and troubleshooting for developers building Micronaut applications. 24 | - Align with coding guidelines: Documentation should complement code by explaining architectural decisions, such as the use of factories, interceptors, and annotation processors. 25 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/MessageSchema.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import io.micronaut.pulsar.schemas.SchemaResolver; 20 | 21 | /** 22 | * Supported schema types. 23 | * 24 | * @author Haris Secic 25 | * @since 1.0 26 | */ 27 | public enum MessageSchema { 28 | 29 | /** 30 | * A sequence of 8-bit unsigned bytes. 31 | */ 32 | BYTES(null), 33 | 34 | /** 35 | * Effectively a `BYTES` schema. 36 | */ 37 | BYTEBUFFER(null), 38 | 39 | /** 40 | * Effectively a `BYTES` schema of a single byte. 41 | */ 42 | INT8(null), 43 | 44 | /** 45 | * A 16-bit signed integer. 46 | */ 47 | INT16(null), 48 | 49 | /** 50 | * A 32-bit signed integer. 51 | */ 52 | INT32(null), 53 | 54 | /** 55 | * A 64-bit signed integer. 56 | */ 57 | INT64(null), 58 | 59 | /** 60 | * A binary value. 61 | */ 62 | BOOL(null), 63 | 64 | /** 65 | * A single precision (32-bit) IEEE 754 floating-point number. 66 | */ 67 | FLOAT(null), 68 | 69 | /** 70 | * A double-precision (64-bit) IEEE 754 floating-point number. 71 | */ 72 | DOUBLE(null), 73 | 74 | /** 75 | * A schema for `java.util.Date` or `java.sql.Date`. 76 | */ 77 | DATE(null), 78 | 79 | /** 80 | * A schema for `java.sql.Time`. 81 | */ 82 | TIME(null), 83 | 84 | /** 85 | * A schema for `java.sql.Timestamp`. 86 | */ 87 | TIMESTAMP(null), 88 | 89 | /** 90 | * A Unicode character sequence. 91 | */ 92 | STRING(null), 93 | 94 | /** 95 | * A schema for JSON data. 96 | */ 97 | JSON(SchemaResolver.JSON_SCHEMA_NAME), 98 | 99 | /** 100 | * An Apache Avro schema. 101 | */ 102 | AVRO(SchemaResolver.AVRO_SCHEMA_NAME), 103 | 104 | /** 105 | * A schema for Protocol Buffer generated messages. 106 | */ 107 | PROTOBUF(SchemaResolver.PROTOBUF_SCHEMA_NAME); 108 | 109 | final String schemaResolverName; 110 | 111 | MessageSchema(@Nullable String namedBean) { 112 | this.schemaResolverName = namedBean; 113 | } 114 | 115 | @Nullable 116 | public String getSchemaResolverName() { 117 | return schemaResolverName; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /.github/workflows/graalvm-dev.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Do not edit this file directly. Instead, go to: 2 | # 3 | # https://github.com/micronaut-projects/micronaut-project-template/tree/master/.github/workflows 4 | # 5 | # and edit them there. Note that it will be sync'ed to all the Micronaut repos 6 | name: GraalVM Dev CI 7 | on: 8 | schedule: 9 | - cron: "0 1 * * 1-5" # Mon-Fri at 1am UTC 10 | jobs: 11 | build_matrix: 12 | if: github.repository != 'micronaut-projects/micronaut-project-template' 13 | runs-on: ubuntu-latest 14 | env: 15 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 16 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 17 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 18 | outputs: 19 | matrix: ${{ steps.build-matrix.outputs.matrix }} 20 | steps: 21 | - uses: actions/checkout@v6 22 | - name: Build Matrix 23 | uses: micronaut-projects/github-actions/graalvm/build-matrix@master 24 | id: build-matrix 25 | build: 26 | needs: build_matrix 27 | if: github.repository != 'micronaut-projects/micronaut-project-template' 28 | runs-on: ubuntu-latest 29 | strategy: 30 | max-parallel: 6 31 | matrix: 32 | java: ['dev', 'latest-ea'] 33 | distribution: ['graalvm-community', 'graalvm'] 34 | native_test_task: ${{ fromJson(needs.build_matrix.outputs.matrix).native_test_task }} 35 | exclude: 36 | - java: 'dev' 37 | distribution: 'graalvm' 38 | - java: 'latest-ea' 39 | distribution: 'graalvm-community' 40 | env: 41 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 42 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 43 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 44 | steps: 45 | - name: Remove system JDKs 46 | run: | 47 | sudo rm -rf /usr/lib/jvm/* 48 | unset JAVA_HOME 49 | export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/usr/lib/jvm' | paste -sd:) 50 | - uses: actions/checkout@v6 51 | - name: Pre-Build Steps 52 | uses: micronaut-projects/github-actions/graalvm/pre-build@master 53 | id: pre-build 54 | with: 55 | java: ${{ matrix.java }} 56 | distribution: ${{ matrix.distribution }} 57 | nativeTestTask: ${{ matrix.native_test_task }} 58 | - name: Build Steps 59 | uses: micronaut-projects/github-actions/graalvm/build@master 60 | id: build 61 | env: 62 | GH_TOKEN_PUBLIC_REPOS_READONLY: ${{ secrets.GH_TOKEN_PUBLIC_REPOS_READONLY }} 63 | GH_USERNAME: ${{ secrets.GH_USERNAME }} 64 | GRAALVM_QUICK_BUILD: true 65 | with: 66 | nativeTestTask: ${{ matrix.native_test_task }} 67 | - name: Post-Build Steps 68 | uses: micronaut-projects/github-actions/graalvm/post-build@master 69 | id: post-build 70 | with: 71 | java: ${{ matrix.java }} 72 | -------------------------------------------------------------------------------- /src/main/docs/guide/producer.adoc: -------------------------------------------------------------------------------- 1 | == Creating producers 2 | 3 | To initialize a producer it's sufficient to annotate an interface with `@PulsarProducerClient` and annotate methods with `@PulsarProducer`. 4 | 5 | snippet::example.Producer[project-base="doc-examples/example", indent="0"] 6 | <1> Annotate interface with @PulsarProducerClient to notify Micronaut for processing it without implementation 7 | <2> Methods are the actual producers so annotate them with @PulsarProducer 8 | <3> Return decides whether to send message in a blocking or non-blocking manner. 9 | <4> A blocking send that waits until message is successfully sent or throws an exception on failure. 10 | 11 | Producers can be used in other types of beans as well (Singleton or such). 12 | 13 | === Producer method 14 | 15 | It's important to note that if the method contains more than 1 argument, `@MessageBody` must be specified on argument 16 | intended for message body as well as `@MessageKey`, `@MessageProperties`, `@MessageHeader` depending on desired mapping. 17 | 18 | WARNING: As with Consumers it's important to spot `@MessageProperties` for collection of headers value but `@MessageHeader` 19 | for single header mapping on method argument. More details in consumer warning. 20 | 21 | === Producer return values 22 | 23 | Not counting wrappers for async behaviour (CompletableFuture, RxJava, reactor cor), abstract methods can only have 2 return 24 | types: void or MessageId since nothing more than sending will be done. 25 | 26 | Non-abstract methods can have any return type except for `MessageId` in the sense that it will not be filled by Pulsars 27 | resulting value. This is because return value must be defined within the method body so there is no reliable way to know 28 | what was the intended return value - was it MessageID from the Pulsar or was it some other MessageID value generated 29 | within the body. 30 | 31 | Methods can be invoked before or after sending the message by setting the property `sendBefore` to `true` or `false` respectively. 32 | Default is false which will execute method invoke message body before calling send message part. 33 | In async approach behaviour is unpredicted as calling the method and sending the message has no blocking 34 | thus message may still be sent before the execution of the method finishes or even starts properly because those can run 35 | on separate threads. This is mainly useful for blocking approach where it's possible to execute the method body 36 | before sending the message an if execution throws exception, sending will be skipped. *First parameter of the method is 37 | still used as a message body*. 38 | 39 | snippet::example.ConsumerProducer[project-base="doc-examples/example", indent="0"] 40 | <1> For Kotlin, open is required in non-interface (abstract) classes because of AOT 41 | <2> - 42 | <3> - 43 | <4> - 44 | <5> Annotating method as a producer 45 | <6> A non-abstract async method. In Kotlin case we need open to allow Micronaut AOT interception. 46 | <7> CompletableFuture or suspend will trigger immediate execution but reactor or RxJava need subscription and will rely 47 | on caller or upper layers to provide it. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Code or Documentation to Micronaut 2 | 3 | ## Finding Issues to Work on 4 | 5 | If you are interested in contributing to Micronaut and are looking for issues to work on, take a look at the issues tagged with [help wanted](https://github.com/micronaut-projects/micronaut-pulsar/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+help+wanted%22). 6 | 7 | ## JDK Setup 8 | 9 | Micronaut Pulsar currently requires JDK 17 10 | 11 | ## IDE Setup 12 | 13 | Micronaut Pulsar can be imported into IntelliJ IDEA by opening the `build.gradle` file. 14 | 15 | ## Docker Setup 16 | 17 | Micronaut Pulsar tests currently require docker to be installed. 18 | 19 | ## Running Tests 20 | 21 | To run the tests use `./gradlew check`. 22 | 23 | ## Building Documentation 24 | 25 | The documentation sources are located at `src/main/docs/guide`. 26 | 27 | To build the documentation run `./gradlew publishGuide` or `./gradlew pG` then open `build/docs/index.html` 28 | 29 | To also build the javadocs instead run `./gradlew docs`. 30 | 31 | ## Working on the code base 32 | 33 | If you are working with the IntelliJ IDEA development environment, you can import the project using the Intellij Gradle Tooling ( "File / Import Project" and select the "settings.gradle" file). 34 | 35 | To get a local development version of Micronaut Pulsar working, first run the `publishToMavenLocal` task. 36 | 37 | ``` 38 | ./gradlew pTML 39 | ``` 40 | 41 | You can then reference the version specified with `projectVersion` in `gradle.properties` in a test project's `build.gradle` or `pom.xml`. 42 | 43 | ## Creating a pull request 44 | 45 | Once you are satisfied with your changes: 46 | 47 | - Commit your changes in your local branch 48 | - Push your changes to your remote branch on GitHub 49 | - Send us a [pull request](https://help.github.com/articles/creating-a-pull-request) 50 | 51 | ## Checkstyle 52 | 53 | We want to keep the code clean, following good practices about organization, javadoc and style as much as possible. 54 | 55 | Micronaut Pulsar uses [Checkstyle](https://checkstyle.sourceforge.io/) to make sure that all the code follows those standards. The configuration file is defined in `config/checkstyle/checkstyle.xml` and to execute the Checkstyle you 56 | need to run: 57 | 58 | ``` 59 | ./gradlew :checkstyleMain 60 | ``` 61 | 62 | Before start contributing with new code it is recommended to install IntelliJ [CheckStyle-IDEA](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea) plugin and configure it to use Micronaut's checkstyle configuration file. 63 | 64 | IntelliJ will mark in red the issues Checkstyle finds. For example: 65 | 66 | ![](https://github.com/micronaut-projects/micronaut-core/raw/master/src/main/docs/resources/img/checkstyle-issue.png) 67 | 68 | In this case, to fix the issues, we need to: 69 | 70 | - Add one empty line before `package` in line 16 71 | - Add the Javadoc for the constructor in line 27 72 | - Add an space after `if` in line 34 73 | 74 | The plugin also adds a new tab in the bottom to run checkstyle report and see all the errors and warnings. It is recommended 75 | to run the report and fixing all the issues before submitting a pull request. 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/schemas/json/JsonReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.schemas.json; 17 | 18 | import io.micronaut.core.type.Argument; 19 | import io.micronaut.json.JsonMapper; 20 | import org.apache.pulsar.client.api.SchemaSerializationException; 21 | import org.apache.pulsar.client.api.schema.SchemaReader; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import java.io.ByteArrayInputStream; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | 29 | /** 30 | * JSON Schema Reader to allow using {@link JsonMapper} from Micronaut instead of shaded one in Pulsar library. 31 | * 32 | * @param POJO type to process. 33 | * @author Haris Secic 34 | * @since 1.0 35 | */ 36 | public final class JsonReader implements SchemaReader { 37 | private static final Logger LOG = LoggerFactory.getLogger(JsonReader.class); 38 | 39 | private final Class pojo; 40 | private final JsonMapper jsonMapper; 41 | 42 | /** 43 | * @param jsonMapper The json mapper to use for reading 44 | * @param pojo The pojo type to read 45 | * @since 1.1.0 46 | */ 47 | public JsonReader(JsonMapper jsonMapper, Class pojo) { 48 | this.pojo = pojo; 49 | this.jsonMapper = jsonMapper; 50 | } 51 | 52 | @Override 53 | public T read(byte[] bytes, int offset, int length) { 54 | try { 55 | return this.jsonMapper.readValue(new ByteArrayInputStream(bytes, offset, length), Argument.of(this.pojo)); 56 | } catch (IOException ex) { 57 | throw new SchemaSerializationException(ex); 58 | } 59 | } 60 | 61 | @Override 62 | public T read(InputStream inputStream) { 63 | final T value; 64 | try { 65 | value = this.jsonMapper.readValue(inputStream, Argument.of(this.pojo)); 66 | try { 67 | inputStream.close(); 68 | } catch (IOException closeException) { 69 | LOG.error("JsonReader close inputStream close error", closeException); 70 | } 71 | } catch (IOException ioException) { 72 | try { 73 | inputStream.close(); 74 | } catch (IOException closeException) { 75 | LOG.error("JsonReader close inputStream close error", closeException); 76 | } 77 | throw new SchemaSerializationException(ioException); 78 | } 79 | // avoiding finally as there's no guarantee for how long it takes to close the stream 80 | // can't use try with resource since resource is passed 81 | return value; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /pulsar-multitenant/src/main/java/io/micronaut/pulsar/processor/TenantNameResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.processor; 17 | 18 | import io.micronaut.core.util.StringUtils; 19 | import io.micronaut.multitenancy.exceptions.TenantNotFoundException; 20 | import io.micronaut.pulsar.config.AbstractPulsarConfiguration; 21 | 22 | import java.io.Serializable; 23 | 24 | /** 25 | * Resolve tenant ID class representation to Apache Pulsar tenant name (as {@link String}). 26 | * 27 | * Tenant identifiers in Micronaut are represented as {@link Serializable} but Apache Pulsar topic needs to know the 28 | * ID of the tenant in text form thus requirement for a {@link String}. 29 | * 30 | * @author Haris 31 | * @since 1.2.0 32 | */ 33 | public interface TenantNameResolver { 34 | 35 | /** 36 | * Resolve tenant name as {@link String} from {@link Serializable}. 37 | * 38 | * @param tenantId Tenant ID resolved from Micronaut {@link io.micronaut.multitenancy.tenantresolver.TenantResolver} 39 | * @return String representation of tenant name for Apache Pulsar. 40 | */ 41 | String resolveTenantNameFromId(Serializable tenantId); 42 | 43 | /** 44 | * Enforce usage of tenant name. 45 | * 46 | * @param tenantName tenant name to enforce on next calls 47 | */ 48 | void overrideTenantName(String tenantName); 49 | 50 | /** 51 | * Clear out enforced tenant name set through {@link #overrideTenantName(String)}. 52 | */ 53 | void clearTenantName(); 54 | 55 | /** 56 | * Resolve current tenant name from tenant ID or overridden value set through {@link #overrideTenantName(String)}. 57 | * 58 | * @return name resolved from current tenant ID if name is not overridden; otherwise value set {@link #overrideTenantName(String)}. 59 | * @throws TenantNotFoundException when override value is missing and no request context is available 60 | */ 61 | String getCurrentTenantName() throws TenantNotFoundException; 62 | 63 | /** 64 | * Check whether tenant name exists in the current request context or is enforced. 65 | * 66 | * @return true if tenant name was set explicitly or available in {@link io.micronaut.multitenancy.tenantresolver.TenantResolver} otherwise false 67 | */ 68 | boolean hasTenantName(); 69 | 70 | /** 71 | * @return Whether tenant name will be resolved only once throughout application lifetime 72 | */ 73 | boolean isStaticTenantResolver(); 74 | 75 | static boolean isValidTenantName(final String tenant) { 76 | if (null == tenant || StringUtils.isEmpty(tenant)) { 77 | return false; 78 | } 79 | return tenant.matches(AbstractPulsarConfiguration.TENANT_NAME_VALIDATOR); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-shared-module/src/test/resources/client.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | 20 | # Configuration for pulsar-client and pulsar-admin CLI tools 21 | 22 | # URL for Pulsar REST API (for admin operations) 23 | # For TLS: 24 | # webServiceUrl=https://localhost:8443/ 25 | webServiceUrl=https://localhost:8443/ 26 | useTls=true 27 | 28 | # URL for Pulsar Binary Protocol (for produce and consume operations) 29 | # For TLS: 30 | # brokerServiceUrl=pulsar+ssl://localhost:6651/ 31 | brokerServiceUrl=pulsar+ssl://localhost:6651/ 32 | 33 | # Authentication plugin to authenticate with servers 34 | # e.g. for TLS 35 | # authPlugin=org.apache.pulsar.client.impl.auth.AuthenticationTls 36 | authPlugin= 37 | 38 | # Parameters passed to authentication plugin. 39 | # A comma separated list of key:value pairs. 40 | # Keys depend on the configured authPlugin. 41 | # e.g. for TLS 42 | # authParams=tlsCertFile:/path/to/client-cert.pem,tlsKeyFile:/path/to/client-key.pem 43 | authParams= 44 | 45 | # Allow TLS connections to servers whose certificate cannot be 46 | # be verified to have been signed by a trusted certificate 47 | # authority. 48 | tlsAllowInsecureConnection=false 49 | 50 | # Whether server hostname must match the common name of the certificate 51 | # the server is using. 52 | tlsEnableHostnameVerification=false 53 | 54 | # Path for the trusted TLS certificate file. 55 | # This cert is used to verify that any cert presented by a server 56 | # is signed by a certificate authority. If this verification 57 | # fails, then the cert is untrusted and the connection is dropped. 58 | tlsTrustCertsFilePath=/my-ca/ca.cert.pem 59 | 60 | # Enable TLS with KeyStore type configuration in broker. 61 | useKeyStoreTls=false 62 | 63 | # TLS KeyStore type configuration: JKS, PKCS12 64 | tlsTrustStoreType=JKS 65 | 66 | # TLS TrustStore path 67 | tlsTrustStorePath= 68 | 69 | # TLS TrustStore password 70 | tlsTrustStorePassword= 71 | 72 | # TLS KeyStore type configuration: JKS, PKCS12 73 | tlsKeyStoreType=JKS 74 | 75 | # TLS TrustStore path 76 | tlsKeyStorePath= 77 | 78 | # TLS TrustStore password 79 | tlsKeyStorePassword= 80 | 81 | # Set up TLS provider for web service 82 | # When TLS authentication with CACert is used, the valid value is either OPENSSL or JDK. 83 | # When TLS authentication with KeyStore is used, available options can be SunJSSE, Conscrypt and so on. 84 | webserviceTlsProvider= 85 | 86 | #Proxy-server URL to which to connect 87 | proxyServiceUrl= 88 | 89 | #Proxy protocol to select type of routing at proxy 90 | proxyProtocol= 91 | 92 | # Pulsar Admin Custom Commands 93 | #customCommandFactoriesDirectory=commandFactories 94 | #customCommandFactories= 95 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/config/PulsarClientConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.config; 17 | 18 | import org.apache.pulsar.client.api.Authentication; 19 | import org.apache.pulsar.client.api.ServiceUrlProvider; 20 | 21 | import java.util.Optional; 22 | import java.util.Set; 23 | 24 | /** 25 | * Basic requirements for custom and default configuration to create Pulsar client. 26 | * 27 | * @author Haris Secic 28 | * @since 1.0 29 | */ 30 | public interface PulsarClientConfiguration { 31 | 32 | String getServiceUrl(); 33 | 34 | /** 35 | * Optional provider for Pulsar services URL. 36 | * @return Optional bean for fetching Pulsar services URLs 37 | */ 38 | default Optional getServiceUrlProvider() { 39 | return Optional.empty(); 40 | } 41 | 42 | /** 43 | * @return Authentication method for pulsar clients 44 | */ 45 | Authentication getAuthentication(); 46 | 47 | /** 48 | * @return Number of threads to allow for Pulsar library. 49 | */ 50 | default Optional getIoThreads() { 51 | return Optional.empty(); 52 | } 53 | 54 | /** 55 | * @return Number of threads to allow for listeners from IO threads defined for Pulsar library 56 | */ 57 | default Optional getListenerThreads() { 58 | return Optional.empty(); 59 | } 60 | 61 | /** 62 | * @return SSL provider if any for Pulsar to client communication encryption 63 | */ 64 | Optional getSslProvider(); 65 | 66 | Optional getTlsTrustStorePath(); 67 | 68 | /** 69 | * @return trust store password if any, trust stores don't contain any sensitive information 70 | * but often work better with password in Java context. 71 | */ 72 | Optional getTlsTrustStorePassword(); 73 | 74 | /** 75 | * @return TLS certificate file path if any for TLS communication between Pulsar and clients. 76 | */ 77 | Optional getTlsCertFilePath(); 78 | 79 | /** 80 | * Useful in development environment for using with local host or such. 81 | * @return Whether to verify TLS certificate host or not. 82 | */ 83 | Optional getTlsVerifyHostname(); 84 | 85 | /** 86 | * @return Allow insecure connection when TLS certificate is set or not. 87 | */ 88 | Optional getTlsAllowInsecureConnection(); 89 | 90 | /** 91 | * Ciphers like TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256. 92 | * @return Permitted ciphers for TLS. 93 | */ 94 | Optional> getTlsCiphers(); 95 | 96 | /** 97 | * Protocols like TLSv1.3, TLSv1.2. 98 | * @return Permitted protocols for TLS. 99 | */ 100 | Optional> getTlsProtocols(); 101 | 102 | /** 103 | * Useful for avoiding hard coding tenant name into every annotation value for producers, 104 | * consumers, or readers. 105 | * @return Default tenant name if any. 106 | */ 107 | default Optional getDefaultTenant() { 108 | return Optional.empty(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /pulsar-multitenant/src/main/java/io/micronaut/pulsar/processor/ToStringTenantNameResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.processor; 17 | 18 | import io.micronaut.context.annotation.Requires; 19 | import io.micronaut.core.annotation.Internal; 20 | import io.micronaut.messaging.exceptions.MessagingException; 21 | import io.micronaut.multitenancy.exceptions.TenantNotFoundException; 22 | import io.micronaut.multitenancy.tenantresolver.FixedTenantResolver; 23 | import io.micronaut.multitenancy.tenantresolver.SystemPropertyTenantResolver; 24 | import io.micronaut.multitenancy.tenantresolver.TenantResolver; 25 | import jakarta.inject.Singleton; 26 | 27 | import java.io.Serializable; 28 | 29 | /** 30 | * Resolve tenant name from {@link java.io.Serializable} object with {@code toString()} or if object is already a 31 | * {@link String}, simply cast. 32 | * 33 | * @author Haris 34 | * @since 1.2.0 35 | */ 36 | @Internal 37 | @Singleton 38 | @Requires(missingBeans = TenantNameResolver.class) 39 | final class ToStringTenantNameResolver implements TenantNameResolver { 40 | 41 | private static final ThreadLocal CURRENT_TENANT = new ThreadLocal<>(); 42 | 43 | private final TenantResolver tenantResolver; 44 | 45 | public ToStringTenantNameResolver(final TenantResolver tenantResolver) { 46 | this.tenantResolver = tenantResolver; 47 | } 48 | 49 | @Override 50 | public String resolveTenantNameFromId(final Serializable tenantId) { 51 | final String tenantName; 52 | if (tenantId instanceof String stringId) { 53 | tenantName = stringId; 54 | } else { 55 | tenantName = tenantId.toString(); 56 | } 57 | return tenantName; 58 | } 59 | 60 | @Override 61 | public void overrideTenantName(final String tenantName) { 62 | if (!TenantNameResolver.isValidTenantName(tenantName)) { 63 | throw new MessagingException(String.format("Invalid tenant name %s", tenantName)); 64 | } 65 | CURRENT_TENANT.set(tenantName); 66 | } 67 | 68 | @Override 69 | public void clearTenantName() { 70 | CURRENT_TENANT.remove(); 71 | } 72 | 73 | @Override 74 | public String getCurrentTenantName() throws TenantNotFoundException { 75 | final var currentTenantName = CURRENT_TENANT.get(); 76 | if (null == currentTenantName) { 77 | return resolveTenantNameFromId(tenantResolver.resolveTenantIdentifier()); 78 | } 79 | return currentTenantName; 80 | } 81 | 82 | @Override 83 | public boolean hasTenantName() { 84 | final String currentTenantName = CURRENT_TENANT.get(); 85 | if (null == currentTenantName) { 86 | try { 87 | tenantResolver.resolveTenantIdentifier(); 88 | return true; 89 | } catch (final TenantNotFoundException ignore) { 90 | return false; 91 | } 92 | } 93 | return true; 94 | } 95 | 96 | @Override 97 | public boolean isStaticTenantResolver() { 98 | return tenantResolver instanceof FixedTenantResolver || tenantResolver instanceof SystemPropertyTenantResolver; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/PulsarClientFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar; 17 | import io.micronaut.context.annotation.Factory; 18 | import io.micronaut.context.annotation.Requires; 19 | import io.micronaut.messaging.exceptions.MessagingClientException; 20 | import io.micronaut.pulsar.config.PulsarClientConfiguration; 21 | import jakarta.inject.Singleton; 22 | import org.apache.pulsar.client.api.PulsarClient; 23 | import org.apache.pulsar.client.impl.ClientBuilderImpl; 24 | 25 | /** 26 | * Create bean of PulsarClient type which is required by consumers and producers. 27 | * 28 | * @author Haris Secic 29 | * @since 1.0 30 | */ 31 | @Factory 32 | @Requires(beans = {PulsarClientConfiguration.class}) 33 | public final class PulsarClientFactory { 34 | 35 | /** 36 | * Simple factory method for building main PulsarClient that serves as a connection to Pulsar cluster. 37 | * 38 | * @param pulsarClientConfiguration Main configuration for building PulsarClient 39 | * @return Instance of {@link PulsarClient} 40 | * @throws MessagingClientException in case any of the required options are missing or malformed 41 | */ 42 | @Singleton 43 | public PulsarClient pulsarClient(final PulsarClientConfiguration pulsarClientConfiguration) throws MessagingClientException { 44 | final ClientBuilderImpl clientBuilder = (ClientBuilderImpl) new ClientBuilderImpl() 45 | .authentication(pulsarClientConfiguration.getAuthentication()); 46 | 47 | if (pulsarClientConfiguration.getServiceUrlProvider().isPresent()) { 48 | clientBuilder.serviceUrlProvider(pulsarClientConfiguration.getServiceUrlProvider().get()); 49 | } else { //Because service URL defaults to localhost it's required to first check for providers 50 | clientBuilder.serviceUrl(pulsarClientConfiguration.getServiceUrl()); 51 | } 52 | 53 | pulsarClientConfiguration.getIoThreads().ifPresent(clientBuilder::ioThreads); 54 | pulsarClientConfiguration.getListenerThreads().ifPresent(clientBuilder::listenerThreads); 55 | pulsarClientConfiguration.getSslProvider().ifPresent(clientBuilder::sslProvider); 56 | pulsarClientConfiguration.getTlsTrustStorePath().ifPresent(clientBuilder::tlsTrustStorePath); 57 | pulsarClientConfiguration.getTlsTrustStorePassword().ifPresent(clientBuilder::tlsTrustStorePassword); 58 | pulsarClientConfiguration.getTlsCertFilePath().ifPresent(clientBuilder::tlsTrustCertsFilePath); 59 | pulsarClientConfiguration.getTlsAllowInsecureConnection().ifPresent(clientBuilder::allowTlsInsecureConnection); 60 | pulsarClientConfiguration.getTlsVerifyHostname().ifPresent(clientBuilder::enableTlsHostnameVerification); 61 | pulsarClientConfiguration.getTlsCiphers().ifPresent(clientBuilder::tlsCiphers); 62 | pulsarClientConfiguration.getTlsProtocols().ifPresent(clientBuilder::tlsProtocols); 63 | 64 | try { 65 | // Pulsar Client performs various targeted optimizations when creating its own EventLoopGroup. 66 | // To preserve these optimizations, we avoid using Micronaut's mechanism and rely on Pulsar's default behavior. 67 | return clientBuilder.build(); 68 | } catch (Exception ex) { 69 | throw new MessagingClientException("Failed to initialize Pulsar Client", ex); 70 | } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/processor/DefaultListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.processor; 17 | 18 | import io.micronaut.inject.DelegatingExecutableMethod; 19 | import io.micronaut.inject.ExecutableMethod; 20 | import org.apache.pulsar.client.api.Consumer; 21 | import org.apache.pulsar.client.api.Message; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import java.util.Map; 26 | import java.util.function.BiConsumer; 27 | 28 | /** 29 | * Default listener for incoming Pulsar messages. 30 | * 31 | * @author Haris Secic 32 | * @since 1.0 33 | */ 34 | @SuppressWarnings({"rawtypes", "unchecked"}) 35 | public class DefaultListener implements MessageListenerResolver { 36 | 37 | private final Logger LOGGER = LoggerFactory.getLogger(DefaultListener.class); 38 | 39 | private final ExecutableMethod method; 40 | private final BiConsumer, Message> receive; 41 | 42 | public DefaultListener(final ExecutableMethod method, 43 | final boolean useMessageWrapper, 44 | final Object invoker, 45 | final PulsarArgumentHandler argumentHandler) { 46 | this.method = method; 47 | final boolean isSuspend; 48 | if (method instanceof DelegatingExecutableMethod) { 49 | isSuspend = ((DelegatingExecutableMethod) method).getTarget().isSuspend(); 50 | } else { 51 | isSuspend = method.isSuspend(); 52 | } 53 | final Map headersOrder = argumentHandler.headersOrder(); 54 | final Map argsOrder = argumentHandler.argumentOrder(); 55 | final int totalArgs = argumentHandler.size(); 56 | final boolean hasHeadersAsMap = argumentHandler.hasHeadersMap(); 57 | receive = (c, v) -> { 58 | final Object[] params = new Object[totalArgs]; 59 | params[argsOrder.get("body")] = useMessageWrapper ? v : v.getValue(); 60 | if (argsOrder.containsKey("consumer")) { 61 | params[argsOrder.get("consumer")] = c; 62 | } 63 | if (argsOrder.containsKey("key")) { 64 | params[argsOrder.get("key")] = v.getKey(); 65 | } 66 | if (hasHeadersAsMap) { 67 | params[argsOrder.get("headers")] = v.getProperties(); 68 | } else { 69 | headersOrder.keySet().forEach(x -> params[headersOrder.get(x)] = v.getProperties().get(x)); 70 | } 71 | if (isSuspend) { 72 | ListenerKotlinHelper.run(method, invoker, params); 73 | } else { 74 | method.invoke(invoker, params); 75 | } 76 | }; 77 | } 78 | 79 | //Pulsar Java lib uses CompletableFutures and has no context/continuation upon the arrival of the message 80 | //so in case of Kotlin we need to create those and do a blocking call within this CompletableFuture 81 | 82 | @Override 83 | public void received(final Consumer consumer, final Message msg) { 84 | try { 85 | receive.accept(consumer, msg); 86 | consumer.acknowledgeAsync(msg); 87 | } catch (Exception ex) { 88 | consumer.negativeAcknowledge(msg.getMessageId()); 89 | LOGGER.error("Could not parse message [{}] for [{}] on method [{}]", msg.getMessageId(), consumer.getConsumerName(), method.getName(), ex); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/src/test/groovy/io/micronaut/pulsar/PulsarProducersSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar 17 | 18 | import io.micronaut.context.annotation.Requires 19 | import io.micronaut.messaging.annotation.MessageBody 20 | import io.micronaut.pulsar.annotation.MessageProperties 21 | import io.micronaut.pulsar.annotation.PulsarProducer 22 | import io.micronaut.pulsar.annotation.PulsarProducerClient 23 | import org.apache.pulsar.client.api.Message 24 | import org.apache.pulsar.client.api.MessageId 25 | import org.apache.pulsar.client.api.PulsarClient 26 | import org.apache.pulsar.client.api.Reader 27 | import org.apache.pulsar.client.impl.schema.StringSchema 28 | import reactor.core.publisher.Mono 29 | import spock.lang.Stepwise 30 | 31 | import static java.util.concurrent.TimeUnit.SECONDS 32 | import static org.apache.pulsar.client.api.MessageId.latest 33 | 34 | @Stepwise 35 | class PulsarProducersSpec extends PulsarAwareTest { 36 | 37 | public static final String PULSAR_PRODUCER_TEST_TOPIC = "persistent://public/default/test.2" 38 | 39 | void "test simple producer"() { 40 | given: 41 | ProducerTester producer = context.getBean(ProducerTester) 42 | Reader reader = context.getBean(PulsarClient) 43 | .newReader(new StringSchema()) 44 | .startMessageId(latest) 45 | .topic(PULSAR_PRODUCER_TEST_TOPIC) 46 | .create() 47 | String message = "This should be received" 48 | 49 | when: 50 | //messages will be read sequentially 51 | producer.producer(message) 52 | String paramReturn = producer.returnOnProduce(message) 53 | MessageId nextMessage = producer.messageIdOnProduce(message) 54 | Mono reactiveMessage = producer.reactiveMessage(message) 55 | producer.withHeaders(message, ["test": "header", "other": "header2"]) 56 | 57 | then: 58 | message == reader.readNext(60, SECONDS).value 59 | paramReturn == reader.readNext(60, SECONDS).value 60 | nextMessage == reader.readNext(60, SECONDS).messageId 61 | reactiveMessage.block() == reader.readNext(60, SECONDS).messageId 62 | Message messageWithHeaders = reader.readNext(60, SECONDS) 63 | messageWithHeaders.hasProperty("test") 64 | messageWithHeaders.getProperty("test") == "header" 65 | 66 | cleanup: 67 | reader.close() 68 | } 69 | 70 | @Requires(property = 'spec.name', value = 'PulsarProducersSpec') 71 | @PulsarProducerClient 72 | static interface ProducerTester { 73 | 74 | @PulsarProducer(topic = PulsarProducersSpec.PULSAR_PRODUCER_TEST_TOPIC, producerName = "test-producer") 75 | void producer(String message); 76 | 77 | @PulsarProducer(topic = PulsarProducersSpec.PULSAR_PRODUCER_TEST_TOPIC, producerName = "test-producer-2") 78 | String returnOnProduce(String message) 79 | 80 | @PulsarProducer(topic = PulsarProducersSpec.PULSAR_PRODUCER_TEST_TOPIC, producerName = "test-producer-3") 81 | MessageId messageIdOnProduce(String message) 82 | 83 | @PulsarProducer(topic = PulsarProducersSpec.PULSAR_PRODUCER_TEST_TOPIC, producerName = "test-producer-reactive") 84 | Mono reactiveMessage(String message) 85 | 86 | @PulsarProducer(topic = PulsarProducersSpec.PULSAR_PRODUCER_TEST_TOPIC, producerName = "test-producer-headers") 87 | MessageId withHeaders(@MessageBody String message, @MessageProperties Map properties) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-multitenant-module/src/test/groovy/io/micronaut/pulsar/FixedTenantTopicResolverSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar 17 | 18 | import io.micronaut.context.ApplicationContext 19 | import io.micronaut.context.annotation.Requires 20 | import io.micronaut.context.env.Environment 21 | import io.micronaut.pulsar.annotation.PulsarConsumer 22 | import io.micronaut.pulsar.annotation.PulsarProducer 23 | import io.micronaut.pulsar.annotation.PulsarProducerClient 24 | import io.micronaut.pulsar.annotation.PulsarSubscription 25 | import io.micronaut.pulsar.shared.PulsarTls 26 | import org.apache.pulsar.client.api.Message 27 | import org.apache.pulsar.client.api.MessageId 28 | import spock.lang.AutoCleanup 29 | import spock.lang.Shared 30 | import spock.lang.Specification 31 | import spock.lang.Stepwise 32 | import spock.util.concurrent.BlockingVariables 33 | 34 | @Stepwise 35 | class FixedTenantTopicResolverSpec extends Specification { 36 | 37 | public static final String PULSAR_FIXED_TENANT_TEST_TOPIC = 'persistent://${tenant}/default/fixedTenantTest' 38 | 39 | @Shared 40 | @AutoCleanup 41 | ApplicationContext context 42 | 43 | void setupSpec() { 44 | PulsarTls.createTopic(FixedTenantTopicResolverSpec.PULSAR_FIXED_TENANT_TEST_TOPIC.replace('${tenant}', 'public')) 45 | context = ApplicationContext.run( 46 | ['pulsar.service-url' : PulsarTls.pulsarBrokerUrl, 47 | 'pulsar.shutdown-on-subscriber-error' : true, 48 | 'spec.name' : getClass().simpleName, 49 | 'micronaut.multitenancy.tenantresolver.fixed.enabled' : true, 50 | 'micronaut.multitenancy.tenantresolver.fixed.tenant-id': 'public'], 51 | Environment.TEST 52 | ) 53 | } 54 | 55 | void "test consumer dynamic tenant name loading in topic"() { 56 | given: 57 | BlockingVariables vars = new BlockingVariables(65) 58 | ConsumerFixedTenantTopicTester dynamicConsumerTester = context.getBean(ConsumerFixedTenantTopicTester.class) 59 | dynamicConsumerTester.blockers = vars 60 | ProducerFixedTenantTopicTester dynamicProducerTester = context.getBean(ProducerFixedTenantTopicTester.class) 61 | String message = 'This should be received' 62 | 63 | when: 64 | MessageId messageId = dynamicProducerTester.send(message) 65 | 66 | then: 67 | null != messageId 68 | messageId == vars.getProperty("messageId") 69 | message == vars.getProperty("value") 70 | } 71 | 72 | @Requires(property = 'spec.name', value = 'FixedTenantTopicResolverSpec') 73 | @PulsarSubscription(subscriptionName = "subscriber-dynamic") 74 | static class ConsumerFixedTenantTopicTester { 75 | BlockingVariables blockers 76 | 77 | @PulsarConsumer( 78 | topic = FixedTenantTopicResolverSpec.PULSAR_FIXED_TENANT_TEST_TOPIC, 79 | consumerName = 'dynamic-topic-consumer', 80 | subscribeAsync = false) 81 | void topicListener(Message message) { 82 | if (null == blockers) { 83 | return 84 | } 85 | blockers.setProperty("messageId", message.messageId) 86 | blockers.setProperty("value", new String(message.value)) 87 | } 88 | } 89 | 90 | @Requires(property = 'spec.name', value = 'FixedTenantTopicResolverSpec') 91 | @PulsarProducerClient 92 | static interface ProducerFixedTenantTopicTester { 93 | @PulsarProducer( 94 | topic = FixedTenantTopicResolverSpec.PULSAR_FIXED_TENANT_TEST_TOPIC, 95 | producerName = 'dynamic-topic-producer') 96 | MessageId send(String message) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test-suite/test-pulsar-module/src/test/groovy/io/micronaut/pulsar/PulsarSchemaSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.pulsar 2 | 3 | import io.micronaut.context.annotation.Requires 4 | import io.micronaut.messaging.annotation.MessageBody 5 | import io.micronaut.pulsar.annotation.PulsarConsumer 6 | import io.micronaut.pulsar.annotation.PulsarProducer 7 | import io.micronaut.pulsar.annotation.PulsarProducerClient 8 | import io.micronaut.pulsar.annotation.PulsarSubscription 9 | import io.micronaut.serde.annotation.Serdeable 10 | import org.apache.pulsar.client.api.Message 11 | import org.apache.pulsar.client.api.MessageId 12 | import spock.lang.Ignore 13 | import spock.lang.Stepwise 14 | import spock.util.concurrent.BlockingVariables 15 | 16 | @Stepwise 17 | class PulsarSchemaSpec extends PulsarAwareTest { 18 | 19 | public static final String PULSAR_JSON_TOPIC = "persistent://public/default/json" 20 | public static final String PULSAR_PROTOBUF_TOPIC = "persistent://public/default/protobuf" 21 | 22 | void "test send receive json"() { 23 | given: 24 | BlockingVariables vars = new BlockingVariables(65) 25 | 26 | when: 27 | PulsarSchemaTestListener topicTester = context.getBean(PulsarSchemaTestListener.class) 28 | topicTester.blockers = vars 29 | PulsarSchemaTestProducer producer = context.getBean(PulsarSchemaTestProducer.class) 30 | //simple consumer with topic list and blocking 31 | JsonMessage message = new JsonMessage(text: "Text value", number: 2) 32 | MessageId jsonId = producer.sendJson(message) 33 | 34 | then: 35 | null != jsonId 36 | jsonId == vars.getProperty("json_message_id") 37 | message.properties == vars.getProperty("json_value").properties 38 | } 39 | 40 | @Ignore 41 | void "test send receive protobuf"() { 42 | given: 43 | BlockingVariables vars = new BlockingVariables(65) 44 | 45 | when: 46 | PulsarSchemaTestListener topicTester = context.getBean(PulsarSchemaTestListener.class) 47 | topicTester.blockers = vars 48 | PulsarSchemaTestProducer producer = context.getBean(PulsarSchemaTestProducer.class) 49 | //simple consumer with topic list and blocking 50 | ProtoMessages.ProtoMessage message = ProtoMessages.ProtoMessage.newBuilder() 51 | .setMessage("Text value") 52 | .setNumber(2) 53 | .build() 54 | MessageId protobufId = producer.sendProto(message) 55 | 56 | then: 57 | null != protobufId 58 | protobufId == vars.getProperty("proto_message_id") 59 | message == vars.getProperty("proto_value") 60 | } 61 | 62 | @Requires(property = 'spec.name', value = 'PulsarSchemaSpec') 63 | @PulsarProducerClient 64 | static interface PulsarSchemaTestProducer { 65 | @PulsarProducer(schema = MessageSchema.JSON, topic = PulsarSchemaSpec.PULSAR_JSON_TOPIC) 66 | MessageId sendJson(JsonMessage message) 67 | 68 | @PulsarProducer(schema = MessageSchema.PROTOBUF, topic = PulsarSchemaSpec.PULSAR_PROTOBUF_TOPIC) 69 | MessageId sendProto(ProtoMessages.ProtoMessage message) 70 | } 71 | 72 | @Requires(property = 'spec.name', value = 'PulsarSchemaSpec') 73 | @PulsarSubscription(subscriptionName = "subscriber-schema") 74 | static class PulsarSchemaTestListener { 75 | BlockingVariables blockers 76 | 77 | @PulsarConsumer( 78 | topic = PulsarSchemaSpec.PULSAR_JSON_TOPIC, 79 | consumerName = 'json-topic-consumer', 80 | subscribeAsync = false) 81 | void jsonListener(Message message) { 82 | if (null == blockers) { 83 | return 84 | } 85 | blockers.setProperty("json_message_id", message.messageId) 86 | blockers.setProperty("json_value", message.getValue()) 87 | } 88 | 89 | @PulsarConsumer( 90 | topic = PulsarSchemaSpec.PULSAR_PROTOBUF_TOPIC, 91 | consumerName = 'protobuf-topic-consumer', 92 | schema = MessageSchema.PROTOBUF, 93 | subscribeAsync = false) 94 | void protobufListener(@MessageBody Message message) { 95 | if (null == blockers) { 96 | return 97 | } 98 | blockers.setProperty("proto_message_id", message.messageId) 99 | blockers.setProperty("proto_value", message.getValue()) 100 | } 101 | } 102 | 103 | @Serdeable 104 | static class JsonMessage { 105 | String text 106 | Integer number 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/PulsarReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.annotation; 17 | 18 | import io.micronaut.aop.Around; 19 | import io.micronaut.aop.Introduction; 20 | import io.micronaut.context.annotation.AliasFor; 21 | import io.micronaut.messaging.annotation.MessageMapping; 22 | import io.micronaut.pulsar.MessageSchema; 23 | import jakarta.validation.constraints.Min; 24 | import org.apache.pulsar.common.schema.KeyValueEncodingType; 25 | 26 | import java.lang.annotation.Documented; 27 | import java.lang.annotation.Retention; 28 | import java.lang.annotation.Target; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | import static io.micronaut.pulsar.MessageSchema.BYTES; 32 | import static java.lang.annotation.ElementType.*; 33 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 34 | 35 | /** 36 | * Create and inject Pulsar reader into field. 37 | * 38 | * @author Haris Secic 39 | * @since 1.0 40 | */ 41 | @Documented 42 | @Retention(RUNTIME) 43 | @Target({PARAMETER, FIELD, METHOD}) 44 | @Around 45 | @Introduction 46 | public @interface PulsarReader { 47 | 48 | /** 49 | * @return topic name to listen to 50 | * @see #topic() 51 | */ 52 | @AliasFor(member = "topic") 53 | @AliasFor(annotation = MessageMapping.class, member = "value") 54 | String value() default ""; 55 | 56 | /** 57 | * Only single topic subscription possible for readers. 58 | * 59 | * @return topic name to listen to 60 | */ 61 | @AliasFor(member = "value") 62 | @AliasFor(annotation = MessageMapping.class, member = "value") 63 | String topic() default ""; 64 | 65 | /** 66 | * @return Subscription to connect to. 67 | */ 68 | String subscriptionName() default ""; 69 | 70 | /** 71 | * Defaults to {@link MessageSchema#BYTES} as default value for Pulsar {@link org.apache.pulsar.client.api.Schema} 72 | * is {@code byte[]}. 73 | * 74 | * @return Schema to use with pulsar topic consumer 75 | */ 76 | MessageSchema schema() default BYTES; 77 | 78 | /** 79 | * If argument annotated with {@link PulsarReader} is of {@link org.apache.pulsar.common.schema.KeyValue} it's 80 | * possible to choose different schema for key transfer. 81 | * 82 | * @return Schema to use while parsing message key from Pulsar message 83 | */ 84 | MessageSchema keySchema() default BYTES; 85 | 86 | /** 87 | * If argument annotated with {@link PulsarReader} is of {@link org.apache.pulsar.common.schema.KeyValue} 88 | * it's possible to choose where to get the message key from. Otherwise, this attribute is ignored. 89 | * 90 | * @return Whether to read key from the message payload or separately. 91 | */ 92 | KeyValueEncodingType keyEncoding() default KeyValueEncodingType.INLINE; 93 | 94 | /** 95 | * @return Reader name. 96 | */ 97 | String readerName() default ""; 98 | 99 | /** 100 | * By default, reader should subscribe in non-blocking manner using default {@link java.util.concurrent.CompletableFuture} 101 | * of {@link org.apache.pulsar.client.api.ConsumerBuilder#subscribeAsync()}. 102 | *

103 | * If blocking is set to false, application thread initializing it will block until consumer is successfully subscribed. 104 | * 105 | * @return Should the consumer subscribe in async manner or blocking 106 | */ 107 | boolean subscribeAsync() default true; 108 | 109 | /** 110 | * @return Whether to position reader to the newest available message in queue or not. 111 | */ 112 | boolean startMessageLatest() default true; 113 | 114 | /** 115 | * Ignored on {@link org.apache.pulsar.client.api.Reader#readNextAsync()}. 116 | * Use -1 for no timeout (default). 117 | * 118 | * @return Maximum allowed read time. 119 | */ 120 | @Min(0) 121 | int readTimeout() default 0; 122 | 123 | /** 124 | * Ignored on {@link org.apache.pulsar.client.api.Reader#readNextAsync()} or if 125 | * {@link #readTimeout()} is 0. 126 | * 127 | * @return Time unit for {@link #readTimeout()}. 128 | */ 129 | TimeUnit timeoutUnit() default TimeUnit.SECONDS; 130 | } 131 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Do not edit this file directly. Instead, go to: 2 | # 3 | # https://github.com/micronaut-projects/micronaut-project-template/tree/master/.github/workflows 4 | # 5 | # and edit them there. Note that it will be sync'ed to all the Micronaut repos 6 | name: Java CI 7 | on: 8 | push: 9 | branches: 10 | - master 11 | - '[0-9]+.[0-9]+.x' 12 | pull_request: 13 | branches: 14 | - master 15 | - '[0-9]+.[0-9]+.x' 16 | jobs: 17 | build: 18 | if: github.repository != 'micronaut-projects/micronaut-project-template' 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | java: ['21', '25'] 23 | env: 24 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 25 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 26 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 27 | GH_TOKEN_PUBLIC_REPOS_READONLY: ${{ secrets.GH_TOKEN_PUBLIC_REPOS_READONLY }} 28 | GH_USERNAME: ${{ secrets.GH_USERNAME }} 29 | TESTCONTAINERS_RYUK_DISABLED: true 30 | PREDICTIVE_TEST_SELECTION: "${{ github.event_name == 'pull_request' && 'true' || 'false' }}" 31 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | OSS_INDEX_USERNAME: ${{ secrets.OSS_INDEX_USERNAME }} 34 | OSS_INDEX_PASSWORD: ${{ secrets.OSS_INDEX_PASSWORD }} 35 | steps: 36 | # https://github.com/actions/virtual-environments/issues/709 37 | - name: Remove system JDKs 38 | run: | 39 | sudo rm -rf /usr/lib/jvm/* 40 | unset JAVA_HOME 41 | export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/usr/lib/jvm' | paste -sd:) 42 | - name: "🗑 Free disk space" 43 | run: | 44 | sudo rm -rf "/usr/local/share/boost" 45 | sudo rm -rf "$AGENT_TOOLSDIRECTORY" 46 | sudo apt-get clean 47 | df -h 48 | 49 | - name: "📥 Checkout repository" 50 | uses: actions/checkout@v6 51 | with: 52 | fetch-depth: 0 53 | 54 | - name: "🔧 Setup GraalVM CE" 55 | uses: graalvm/setup-graalvm@v1.4.4 56 | with: 57 | distribution: 'graalvm' 58 | java-version: ${{ matrix.java }} 59 | github-token: ${{ secrets.GITHUB_TOKEN }} 60 | 61 | - name: "🔧 Setup Gradle" 62 | uses: gradle/actions/setup-gradle@v5 63 | 64 | - name: "❓ Optional setup step" 65 | run: | 66 | [ -f ./setup.sh ] && ./setup.sh || [ ! -f ./setup.sh ] 67 | 68 | - name: "🚔 Sonatype Scan" 69 | if: env.OSS_INDEX_PASSWORD != '' && matrix.java == '21' 70 | id: sonatypescan 71 | run: | 72 | ./gradlew ossIndexAudit --no-parallel --info 73 | 74 | - name: "🛠 Build with Gradle" 75 | id: gradle 76 | run: | 77 | ./gradlew check jacocoReport --no-daemon --continue 78 | 79 | - name: "🔎 Run static analysis" 80 | if: env.SONAR_TOKEN != '' && matrix.java == '21' 81 | run: | 82 | ./gradlew sonar --no-parallel --continue 83 | 84 | - name: "📊 Publish Test Report" 85 | if: always() 86 | uses: mikepenz/action-junit-report@v6 87 | with: 88 | check_name: Java CI / Test Report (${{ matrix.java }}) 89 | report_paths: '**/build/test-results/test/TEST-*.xml' 90 | check_retries: 'true' 91 | 92 | - name: "📜 Upload binary compatibility check results" 93 | if: matrix.java == '21' 94 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 95 | with: 96 | name: binary-compatibility-reports 97 | path: "**/build/reports/binary-compatibility-*.html" 98 | 99 | - name: "📦 Publish to Sonatype Snapshots" 100 | if: success() && github.event_name == 'push' && matrix.java == '21' 101 | env: 102 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 103 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 104 | run: ./gradlew publishToSonatype docs --no-daemon 105 | 106 | - name: "❓ Determine docs target repository" 107 | uses: haya14busa/action-cond@v1 108 | id: docs_target 109 | with: 110 | cond: ${{ github.repository == 'micronaut-projects/micronaut-core' }} 111 | if_true: "micronaut-projects/micronaut-docs" 112 | if_false: ${{ github.repository }} 113 | 114 | - name: "📑 Publish to Github Pages" 115 | if: success() && github.event_name == 'push' && matrix.java == '21' 116 | uses: micronaut-projects/github-pages-deploy-action@master 117 | env: 118 | TARGET_REPOSITORY: ${{ steps.docs_target.outputs.value }} 119 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 120 | BRANCH: gh-pages 121 | FOLDER: build/docs 122 | 123 | - name: "❓ Optional cleanup step" 124 | run: | 125 | [ -f ./cleanup.sh ] && ./cleanup.sh || [ ! -f ./cleanup.sh ] 126 | -------------------------------------------------------------------------------- /pulsar/src/main/java/io/micronaut/pulsar/annotation/PulsarProducer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2022 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.pulsar.annotation; 17 | 18 | import io.micronaut.aop.Around; 19 | import io.micronaut.aop.Introduction; 20 | import io.micronaut.context.annotation.AliasFor; 21 | import io.micronaut.context.annotation.Type; 22 | import io.micronaut.pulsar.MessageSchema; 23 | import io.micronaut.pulsar.intercept.PulsarProducerAdvice; 24 | import org.apache.pulsar.client.api.CompressionType; 25 | import org.apache.pulsar.client.api.HashingScheme; 26 | import org.apache.pulsar.client.api.MessageRoutingMode; 27 | import org.apache.pulsar.common.schema.KeyValueEncodingType; 28 | 29 | import java.lang.annotation.Documented; 30 | import java.lang.annotation.Retention; 31 | import java.lang.annotation.Target; 32 | 33 | import static io.micronaut.pulsar.MessageSchema.BYTES; 34 | import static java.lang.annotation.ElementType.METHOD; 35 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 36 | import static org.apache.pulsar.client.api.CompressionType.NONE; 37 | import static org.apache.pulsar.client.api.HashingScheme.JavaStringHash; 38 | import static org.apache.pulsar.client.api.MessageRoutingMode.RoundRobinPartition; 39 | 40 | /** 41 | * Marks a method that should produce values to Pulsar topics on call. 42 | * 43 | * @author Haris Secic 44 | * @since 1.0 45 | */ 46 | @Documented 47 | @Retention(RUNTIME) 48 | @Target(METHOD) 49 | @Around 50 | @Introduction 51 | @Type(PulsarProducerAdvice.class) 52 | public @interface PulsarProducer { 53 | 54 | /** 55 | * @return Same as {@link #topic()} 56 | */ 57 | @AliasFor(member = "topic") 58 | String value() default ""; 59 | 60 | /** 61 | * @return Producer name. 62 | */ 63 | String producerName() default ""; 64 | 65 | /** 66 | * @return Topic to produce messages to 67 | */ 68 | @AliasFor(member = "value") 69 | String topic() default ""; 70 | 71 | /** 72 | * @return Type of message serialization. 73 | */ 74 | MessageSchema schema() default BYTES; 75 | 76 | /** 77 | * @return Type of message key serialization. 78 | */ 79 | MessageSchema keySchema() default BYTES; 80 | 81 | /** 82 | * If no {@link MessageKey} annotated method argument is detected this attribute is ignored and message is treated 83 | * as simple - without a key. 84 | * 85 | * @return Whether key will be in the message payload or separate. 86 | */ 87 | KeyValueEncodingType keyEncoding() default KeyValueEncodingType.INLINE; 88 | 89 | /** 90 | * @return Compression type. 91 | */ 92 | CompressionType compressionType() default NONE; 93 | 94 | /** 95 | * @return Message routing mode. 96 | */ 97 | MessageRoutingMode messageRoutingMode() default RoundRobinPartition; 98 | 99 | /** 100 | * @return Produce messages of different schemas than specified at creation time 101 | */ 102 | boolean multiSchema() default true; 103 | 104 | /** 105 | * @return Discover new partitions at runtime 106 | */ 107 | boolean autoUpdatePartition() default true; 108 | 109 | /** 110 | * @return Multiple calls to send and sendAsync will block if queue full 111 | */ 112 | boolean blockQueue() default false; 113 | 114 | /** 115 | * @return Enabled automatic batching of messages 116 | */ 117 | boolean batching() default true; 118 | 119 | /** 120 | * @return Max messages in one batch 121 | */ 122 | int batchingMaxMessages() default 1000; 123 | 124 | /** 125 | * Default 128KB. 126 | * 127 | * @return Max bytes per batch 128 | */ 129 | int batchingMaxBytes() default 1024 * 128; 130 | 131 | /** 132 | * If this is enabled batching should be disabled. 133 | * 134 | * @return Split messages in chunks if bigger than max allowed size. 135 | */ 136 | boolean chunking() default false; 137 | 138 | /** 139 | * @return Encryption key to add 140 | */ 141 | String encryptionKey() default ""; 142 | 143 | /** 144 | * @return Starting sequence ID for producer 145 | */ 146 | long initialSequenceId() default Long.MIN_VALUE; 147 | 148 | /** 149 | * @return Change hashing scheme used to choose partition 150 | */ 151 | HashingScheme hashingScheme() default JavaStringHash; 152 | 153 | /** 154 | * Defaults to false. 155 | * Will be used to determine whether to send message prior to executing method code or after. 156 | * @return Whether to send the message before calling actual implementation. 157 | */ 158 | boolean sendBefore() default false; 159 | } 160 | --------------------------------------------------------------------------------