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 |
--------------------------------------------------------------------------------