├── .gitignore
├── LICENSE
├── README.md
├── akka
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── lightbend
│ │ └── microprofile
│ │ └── reactive
│ │ └── messaging
│ │ └── akka
│ │ ├── AkkaProvider.java
│ │ └── AkkaProviderExtension.java
│ └── resources
│ └── META-INF
│ └── services
│ └── javax.enterprise.inject.spi.Extension
├── bin
├── buildDeps.sh
├── installKafka.sh
├── startKafka.sh
└── stopKafka.sh
├── core
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── lightbend
│ │ └── microprofile
│ │ └── reactive
│ │ └── messaging
│ │ ├── impl
│ │ ├── FlowUtils.java
│ │ ├── LightbendReactiveMessagingCdiExtension.java
│ │ ├── MessageWithCause.java
│ │ ├── PublishingStreamImpl.java
│ │ ├── Reflections.java
│ │ ├── StreamDescriptor.java
│ │ ├── StreamDescriptorPort.java
│ │ ├── StreamManager.java
│ │ ├── StreamManagerImpl.java
│ │ ├── StreamRunner.java
│ │ ├── StreamShape.java
│ │ ├── StreamsInjectionTarget.java
│ │ ├── SubscribingStreamImpl.java
│ │ ├── ValidatedProcessingStreamRunner.java
│ │ ├── ValidatedPublishingStreamRunner.java
│ │ └── ValidatedSubscribingStreamRunner.java
│ │ ├── jsonb
│ │ ├── JsonbMessageDeserializer.java
│ │ ├── JsonbMessageSerializer.java
│ │ └── JsonbSerializationSupport.java
│ │ └── spi
│ │ ├── LightbendMessagingProvider.java
│ │ ├── MessageDeserializer.java
│ │ ├── MessageSerializer.java
│ │ ├── PublishingStream.java
│ │ ├── SerializationSupport.java
│ │ ├── SubscribingStream.java
│ │ ├── ValidatedPublishingStream.java
│ │ └── ValidatedSubscribingStream.java
│ └── resources
│ └── META-INF
│ └── services
│ └── javax.enterprise.inject.spi.Extension
├── example
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── example
│ │ ├── ActorSystemProducer.java
│ │ ├── Application.java
│ │ └── MyMessageHandler.java
│ └── resources
│ └── META-INF
│ └── beans.xml
├── kafka-tck
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── lightbend
│ │ │ └── microprofile
│ │ │ └── reactive
│ │ │ └── messaging
│ │ │ └── kafka
│ │ │ └── tck
│ │ │ ├── client
│ │ │ ├── KafkaAdminClientListener.java
│ │ │ ├── KafkaArquillianTckExtension.java
│ │ │ ├── KafkaTckAuxilleryArchiveAppender.java
│ │ │ └── KafkaTopicDeployListener.java
│ │ │ └── container
│ │ │ ├── KafkaArquillianTckRemoteExtension.java
│ │ │ ├── KafkaContainerListener.java
│ │ │ └── KafkaTckMessagingPuppet.java
│ └── resources
│ │ ├── META-INF
│ │ └── services
│ │ │ └── org.jboss.arquillian.core.spi.LoadableExtension
│ │ ├── application.conf
│ │ └── log4j.properties
│ └── test
│ └── java
│ └── com
│ └── lightbend
│ └── microprofile
│ └── reactive
│ └── messaging
│ └── kafka
│ └── KafkaReactiveMessagingTckVerificationTest.java
├── kafka
├── out
│ ├── production
│ │ └── resources
│ │ │ └── META-INF
│ │ │ └── services
│ │ │ └── javax.enterprise.inject.spi.Extension
│ └── test
│ │ └── resources
│ │ └── META-INF
│ │ └── services
│ │ └── org.eclipse.microprofile.reactive.messaging.tck.spi.TckContainer
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── lightbend
│ │ └── microprofile
│ │ └── reactive
│ │ └── messaging
│ │ └── kafka
│ │ ├── Kafka.java
│ │ ├── KafkaCdiExtension.java
│ │ ├── KafkaConsumerMessage.java
│ │ ├── KafkaConsumerMessageImpl.java
│ │ ├── KafkaConsumerSettings.java
│ │ ├── KafkaInstanceProvider.java
│ │ ├── KafkaMessagingProvider.java
│ │ ├── KafkaProducerMessage.java
│ │ ├── KafkaProducerSettings.java
│ │ ├── KafkaValidatedPublishingStream.java
│ │ └── KafkaValidatedSubscribingStream.java
│ └── resources
│ └── META-INF
│ └── services
│ └── javax.enterprise.inject.spi.Extension
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | local-kafka-install
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lightbend MicroProfile Reactive Messaging
2 |
3 | This provides an implementation of MicroProfile Reactive Messaging built on Akka Streams and Alpakka.
4 |
5 | ## Developing
6 |
7 | Currently due to the state of very active of the specs it depends on, it depends on snapshot versions of a number of things. You can use the `bin/buildDeps.sh` script to checkout, build and install the dependencies locally. The `buildDeps.sh` script hardcodes the commit hash of the repo that it depends on, so if you want to upgrade, you must update the commit hashes in that script.
8 |
9 | ## Kafka support
10 |
11 | Currently, Kafka is supported as a messaging provider.
12 |
13 | To work with it or run the tests, you'll need a copy of Kafka installed. Convenience scripts exist to download, install and run Kafka exist in the `bin` directory, including `installKafka.sh`, `startKafka.sh` and `runKafka.sh`. These install kafka into the root directory of this repo, in a directory called `local-kafka-install`.
14 |
15 | The tests for the Kafka support have their own artifact, this is so that those tests can be depended upon (in a profile) by the MicroProfile Reactive Messaging TCK itself, to allow for faster development of the TCK.
16 |
--------------------------------------------------------------------------------
/akka/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 | 4.0.0
8 |
9 |
10 | com.lightbend.microprofile.reactive.messaging
11 | lightbend-microprofile-reactive-messaging-parent
12 | 1.0-SNAPSHOT
13 |
14 |
15 | lightbend-microprofile-reactive-messaging-akka
16 | Lightbend MicroProfile Reactive Messaging Akka
17 | Lightbend MicroProfile Reactive Messaging :: Akka Provider
18 |
19 |
20 | ${project.groupId}
21 | lightbend-microprofile-reactive-messaging
22 | ${project.version}
23 |
24 |
25 |
26 |
27 |
28 |
29 | org.apache.maven.plugins
30 | maven-javadoc-plugin
31 |
32 |
33 | attach-javadocs
34 |
35 | jar
36 |
37 |
38 |
39 |
40 |
41 | org.apache.maven.plugins
42 | maven-source-plugin
43 |
44 |
45 | attach-sources
46 |
47 | jar
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/akka/src/main/java/com/lightbend/microprofile/reactive/messaging/akka/AkkaProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.akka;
5 |
6 | import akka.actor.ActorSystem;
7 | import akka.stream.ActorMaterializer;
8 | import akka.stream.Materializer;
9 |
10 | import javax.annotation.PreDestroy;
11 | import javax.enterprise.context.ApplicationScoped;
12 | import javax.enterprise.inject.Produces;
13 |
14 | @ApplicationScoped
15 | public class AkkaProvider {
16 | private final ActorSystem system = ActorSystem.create();
17 | private final Materializer materializer = ActorMaterializer.create(system);
18 |
19 | @Produces
20 | public ActorSystem createActorSystem() {
21 | return system;
22 | }
23 |
24 | @Produces
25 | public Materializer provideMaterializer() {
26 | return materializer;
27 | }
28 |
29 | @PreDestroy
30 | public void shutdownActorSystem() {
31 | system.terminate();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/akka/src/main/java/com/lightbend/microprofile/reactive/messaging/akka/AkkaProviderExtension.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.akka;
5 |
6 | import javax.enterprise.event.Observes;
7 | import javax.enterprise.inject.spi.BeforeBeanDiscovery;
8 | import javax.enterprise.inject.spi.Extension;
9 |
10 | public class AkkaProviderExtension implements Extension {
11 |
12 | public void registerAkkaProvider(@Observes BeforeBeanDiscovery bbd) {
13 | bbd.addAnnotatedType(AkkaProvider.class, AkkaProvider.class.getName());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/akka/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension:
--------------------------------------------------------------------------------
1 | com.lightbend.microprofile.reactive.messaging.akka.AkkaProviderExtension
--------------------------------------------------------------------------------
/bin/buildDeps.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This should be set to the commit hash that is being tracked.
4 | MP_REACTIVE_COMMIT="e72a11b"
5 | # To track a particular pull request, put it's number here, otherwise comment it out.
6 | # MP_REACTIVE_PR="69"
7 |
8 | LIGHTBEND_MP_STREAMS_COMMIT="a53a114"
9 | #LIGHTBEND_MP_STREAMS_PR=""
10 |
11 | set -e
12 |
13 | cd "$( dirname "${BASH_SOURCE[0]}" )/.."
14 | mkdir -p target
15 | cd target
16 |
17 | if [[ -d microprofile-reactive ]]; then
18 | cd microprofile-reactive
19 | git fetch
20 | else
21 | git clone https://github.com/eclipse/microprofile-reactive.git
22 | cd microprofile-reactive
23 | fi
24 |
25 | if [[ -n ${MP_REACTIVE_PR+x} ]]; then
26 | git fetch origin "pull/${MP_REACTIVE_PR}/head"
27 | fi
28 |
29 | git checkout "${MP_REACTIVE_COMMIT}"
30 |
31 | mvn clean install -Dmaven.test.skip -Drat.skip=true -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true -Dasciidoctor.skip=true
32 |
33 | cd ..
34 |
35 | if [[ -d lightbend-microprofile-reactive-streams ]]; then
36 | cd lightbend-microprofile-reactive-streams
37 | git fetch
38 | else
39 | git clone https://github.com/lightbend/microprofile-reactive-streams.git lightbend-microprofile-reactive-streams
40 | cd lightbend-microprofile-reactive-streams
41 | fi
42 |
43 | if [[ -n ${LIGHTBEND_MP_STREAMS_PR+x} ]]; then
44 | git fetch origin "pull/${LIGHTBEND_MP_STREAMS_PR}/head"
45 | fi
46 |
47 | git checkout "${LIGHTBEND_MP_STREAMS_COMMIT}"
48 |
49 | mvn clean install -Dmaven.test.skip -Drat.skip=true -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true -Dasciidoctor.skip=true
50 |
--------------------------------------------------------------------------------
/bin/installKafka.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | KAFKA_VERSION=1.1.0
4 | KAFKA_INSTALL_DIR=local-kafka-install
5 |
6 | set -e
7 |
8 | cd "$( dirname "${BASH_SOURCE[0]}" )/.."
9 | if [[ -d ${KAFKA_INSTALL_DIR} ]]; then
10 | echo "Kafka already installed."
11 | else
12 | mkdir -p ${KAFKA_INSTALL_DIR}
13 | cd ${KAFKA_INSTALL_DIR}
14 | wget http://www.us.apache.org/dist/kafka/${KAFKA_VERSION}/kafka_2.11-${KAFKA_VERSION}.tgz -O kafka.tgz
15 | tar xzf kafka.tgz --strip-components 1
16 | fi
17 |
--------------------------------------------------------------------------------
/bin/startKafka.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | KAFKA_INSTALL_DIR=local-kafka-install
4 |
5 | set -e
6 |
7 | cd "$( dirname "${BASH_SOURCE[0]}" )/.."
8 |
9 | if [[ -d "${KAFKA_INSTALL_DIR}" ]]; then
10 | cd "${KAFKA_INSTALL_DIR}"
11 | bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
12 | bin/kafka-server-start.sh -daemon config/server.properties
13 | echo "ZooKeeper and Kafka started."
14 | else
15 | echo "Kafka has not been installed in ${KAFKA_INSTALL_DIR}, please run bin/installKafka.sh first."
16 | exit 1
17 | fi
18 |
--------------------------------------------------------------------------------
/bin/stopKafka.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | KAFKA_INSTALL_DIR=local-kafka-install
4 |
5 | set -e
6 |
7 | cd "$( dirname "${BASH_SOURCE[0]}" )/.."
8 |
9 | if [[ -d "${KAFKA_INSTALL_DIR}" ]]; then
10 | cd "${KAFKA_INSTALL_DIR}"
11 | bin/kafka-server-stop.sh
12 | bin/zookeeper-server-stop.sh
13 | else
14 | echo "Kafka has not been installed in ${KAFKA_INSTALL_DIR}, please run bin/installKafka.sh first."
15 | exit 1
16 | fi
17 |
--------------------------------------------------------------------------------
/core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 | 4.0.0
8 |
9 |
10 | com.lightbend.microprofile.reactive.messaging
11 | lightbend-microprofile-reactive-messaging-parent
12 | 1.0-SNAPSHOT
13 |
14 |
15 | lightbend-microprofile-reactive-messaging
16 | Lightbend MicroProfile Reactive Messaging
17 | Lightbend MicroProfile Reactive Messaging :: Core library
18 |
19 |
20 | org.eclipse.microprofile.reactive.messaging
21 | microprofile-reactive-messaging-api
22 |
23 |
24 | com.lightbend.microprofile.reactive.streams
25 | lightbend-microprofile-reactive-streams-akka
26 |
27 |
28 | javax.enterprise
29 | cdi-api
30 |
31 |
32 | javax.json.bind
33 | javax.json.bind-api
34 |
35 |
36 |
37 |
38 |
39 |
40 | org.apache.maven.plugins
41 | maven-javadoc-plugin
42 |
43 |
44 | attach-javadocs
45 |
46 | jar
47 |
48 |
49 |
50 |
51 |
52 | org.apache.maven.plugins
53 | maven-source-plugin
54 |
55 |
56 | attach-sources
57 |
58 | jar
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/FlowUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import akka.NotUsed;
7 | import akka.japi.Pair;
8 | import akka.stream.*;
9 | import akka.stream.javadsl.Broadcast;
10 | import akka.stream.javadsl.Flow;
11 | import akka.stream.javadsl.GraphDSL;
12 | import akka.stream.javadsl.Source;
13 | import akka.stream.javadsl.Zip;
14 | import akka.stream.stage.AbstractInHandler;
15 | import akka.stream.stage.AbstractOutHandler;
16 | import akka.stream.stage.AsyncCallback;
17 | import akka.stream.stage.GraphStage;
18 | import akka.stream.stage.GraphStageLogic;
19 | import akka.stream.stage.InHandler;
20 | import akka.stream.stage.OutHandler;
21 | import org.eclipse.microprofile.reactive.messaging.Message;
22 |
23 | import java.lang.reflect.InvocationTargetException;
24 | import java.util.concurrent.CompletableFuture;
25 | import java.util.concurrent.CompletionStage;
26 |
27 | public class FlowUtils {
28 | private FlowUtils() {}
29 |
30 | public static Flow, Message, NotUsed> bypassFlow(Flow, Message, ?> flow) {
31 | return Flow.fromGraph(GraphDSL.create(builder -> {
32 | UniformFanOutShape, Message> bcast = builder.add(Broadcast.create(2));
33 | FanInShape2, Message, Pair, Message>> zip =
34 | builder.add(Zip.create());
35 |
36 | // this ensures that the user side of the flow is allowed to do buffering, otherwise the bypass side will not pull,
37 | // preventing the broadcast from sending more elements.
38 | // todo make buffer size configurable
39 | Flow, Message, NotUsed> bypassBuffer = Flow.>create().buffer(16, OverflowStrategy.backpressure());
40 |
41 | builder.from(bcast).via(builder.add(flow)).toInlet(zip.in0());
42 | builder.from(bcast).via(builder.add(bypassBuffer)).toInlet(zip.in1());
43 |
44 | Flow, Message>, Message, NotUsed> zipF = Flow., Message>>create().map(pair ->
45 | new MessageWithCause<>(pair.second(), pair.first())
46 | );
47 |
48 | return FlowShape.of(bcast.in(), builder.from(zip.out()).via(builder.add(zipF)).out());
49 | }));
50 | }
51 |
52 | public static Flow asynchronouslyProvidedFlow(CompletionStage, NotUsed>> flow) {
53 | return Flow.fromGraph(new AsynchronouslyProvidedFlow<>(flow));
54 | }
55 |
56 | private static class AsynchronouslyProvidedFlow extends GraphStage> {
57 |
58 | private final CompletionStage, NotUsed>> asyncFlow;
59 |
60 | private AsynchronouslyProvidedFlow(CompletionStage, NotUsed>> flow) {
61 | this.asyncFlow = flow;
62 | }
63 |
64 | private final Inlet in = Inlet.create("AsyncProvidedFlow.in");
65 | private final Outlet out = Outlet.create("AsyncProvidedFlow.out");
66 | private final FlowShape shape = FlowShape.of(in, out);
67 |
68 | @Override
69 | public FlowShape shape() {
70 | return shape;
71 | }
72 |
73 | @Override
74 | public GraphStageLogic createLogic(Attributes inheritedAttributes) {
75 |
76 | return new AsyncProvidedFlowLogic();
77 | }
78 |
79 | private class AsyncProvidedFlowLogic extends GraphStageLogic {
80 |
81 | {
82 | setHandler(in, new AbstractInHandler() {
83 | @Override
84 | public void onPush() throws Exception {
85 | }
86 | });
87 | setHandler(out, new AbstractOutHandler() {
88 | @Override
89 | public void onPull() throws Exception {
90 | }
91 | });
92 | }
93 |
94 | public AsyncProvidedFlowLogic() {
95 | super(AsynchronouslyProvidedFlow.this.shape);
96 | }
97 |
98 | private SubSinkInlet createSubSinkInlet() {
99 | return instantiateSubPort(SubSinkInlet.class, "AsyncProvidedFlow.subIn");
100 | }
101 |
102 | private SubSourceOutlet createSubSourceOutlet() {
103 | return instantiateSubPort(SubSourceOutlet.class, "AsyncProvidedFlow.subOut");
104 | }
105 |
106 | /**
107 | * This hack is necessary due to https://github.com/scala/bug/issues/10889.
108 | *
109 | * The problem is, javac recognises the class as a static inner class that you need to pass a reference
110 | * to the parent to in its constructor, however, it is not a static inner class, and the resulting byte
111 | * code that it emits is binary incompatible with the actual class.
112 | */
113 | private C instantiateSubPort(Class clazz, String name) {
114 | try {
115 | return clazz.getDeclaredConstructor(GraphStageLogic.class, String.class).newInstance(this, name);
116 | }
117 | catch (Exception e) {
118 | throw new RuntimeException("Unable to instantiate sub port by reflection", e);
119 | }
120 | }
121 |
122 | @Override
123 | public void preStart() {
124 | AsyncCallback, NotUsed>> callback = createAsyncCallback(flow -> {
125 |
126 | final SubSinkInlet subIn = createSubSinkInlet();
127 | subIn.setHandler(new InHandler() {
128 | @Override
129 | public void onPush() throws Exception {
130 | push(out, subIn.grab());
131 | }
132 |
133 | @Override
134 | public void onUpstreamFinish() throws Exception {
135 | complete(out);
136 | }
137 |
138 | @Override
139 | public void onUpstreamFailure(Throwable ex) throws Exception {
140 | fail(out, ex);
141 | }
142 | });
143 |
144 | setHandler(out, new OutHandler() {
145 | @Override
146 | public void onPull() throws Exception {
147 | subIn.pull();
148 | }
149 |
150 | @Override
151 | public void onDownstreamFinish() throws Exception {
152 | subIn.cancel();
153 | }
154 | });
155 |
156 | final SubSourceOutlet subOut = createSubSourceOutlet();
157 | subOut.setHandler(new OutHandler() {
158 | @Override
159 | public void onDownstreamFinish() throws Exception {
160 | cancel(in);
161 | }
162 |
163 | @Override
164 | public void onPull() throws Exception {
165 | pull(in);
166 | }
167 | });
168 |
169 | setHandler(in, new InHandler() {
170 | @Override
171 | public void onPush() throws Exception {
172 | subOut.push(grab(in));
173 | }
174 |
175 | @Override
176 | public void onUpstreamFinish() throws Exception {
177 | subOut.complete();
178 | }
179 |
180 | @Override
181 | public void onUpstreamFailure(Throwable ex) throws Exception {
182 | subOut.fail(ex);
183 | }
184 | });
185 |
186 | Source.fromGraph(subOut.source())
187 | .via(flow)
188 | .runWith(subIn.sink(), subFusingMaterializer());
189 |
190 | if (isAvailable(out)) {
191 | subIn.pull();
192 | }
193 | });
194 |
195 | asyncFlow.thenAccept(callback::invoke);
196 | }
197 | }
198 | }
199 |
200 |
201 | }
202 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/LightbendReactiveMessagingCdiExtension.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import akka.japi.Pair;
7 | import akka.stream.javadsl.Flow;
8 | import akka.stream.javadsl.Sink;
9 | import akka.stream.javadsl.Source;
10 | import com.google.common.reflect.TypeToken;
11 | import org.eclipse.microprofile.reactive.messaging.Incoming;
12 | import org.eclipse.microprofile.reactive.messaging.Message;
13 | import org.eclipse.microprofile.reactive.messaging.Outgoing;
14 | import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder;
15 | import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder;
16 | import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder;
17 | import org.reactivestreams.Processor;
18 | import org.reactivestreams.Publisher;
19 | import org.reactivestreams.Subscriber;
20 |
21 | import javax.enterprise.context.ApplicationScoped;
22 | import javax.enterprise.context.Initialized;
23 | import javax.enterprise.event.Observes;
24 | import javax.enterprise.inject.spi.AfterDeploymentValidation;
25 | import javax.enterprise.inject.spi.AnnotatedMethod;
26 | import javax.enterprise.inject.spi.AnnotatedType;
27 | import javax.enterprise.inject.spi.Bean;
28 | import javax.enterprise.inject.spi.BeanManager;
29 | import javax.enterprise.inject.spi.BeforeBeanDiscovery;
30 | import javax.enterprise.inject.spi.DefinitionException;
31 | import javax.enterprise.inject.spi.Extension;
32 | import javax.enterprise.inject.spi.ProcessInjectionTarget;
33 | import java.lang.reflect.ParameterizedType;
34 | import java.lang.reflect.Type;
35 | import java.util.ArrayList;
36 | import java.util.Collection;
37 | import java.util.List;
38 | import java.util.Optional;
39 | import java.util.concurrent.CompletionStage;
40 |
41 | public class LightbendReactiveMessagingCdiExtension implements Extension {
42 |
43 | private final Collection> allStreamingBeans = new ArrayList<>();
44 |
45 | public void registerBeans(@Observes BeforeBeanDiscovery bbd) {
46 | bbd.addAnnotatedType(StreamManagerImpl.class, StreamManagerImpl.class.getName());
47 | }
48 |
49 | public void locateStreams(@Observes ProcessInjectionTarget bean, BeanManager beanManager) {
50 |
51 | // Find all the stream annotated methods, and read them.
52 | List streams = locateStreams(bean.getAnnotatedType());
53 |
54 | if (!streams.isEmpty()) {
55 | StreamsInjectionTarget newTarget = new StreamsInjectionTarget<>(beanManager, bean.getAnnotatedType(), bean.getInjectionTarget(), streams);
56 | allStreamingBeans.add(newTarget);
57 | bean.setInjectionTarget(newTarget);
58 | }
59 | }
60 |
61 | public void validateStreams(@Observes AfterDeploymentValidation adv) {
62 | for (StreamsInjectionTarget> streamingBean : allStreamingBeans) {
63 | streamingBean.validate();
64 | }
65 | }
66 |
67 | public void startApplicationScopedStreams(@Observes @Initialized(ApplicationScoped.class) Object obj, BeanManager beanManager) {
68 | for (StreamsInjectionTarget> streamingBean : allStreamingBeans) {
69 |
70 | AnnotatedType> type = streamingBean.getBeanType();
71 | // todo do we need to worry about qualifiers?
72 | for (Bean> bean: beanManager.getBeans(type.getBaseType())) {
73 | if (bean.getScope().equals(ApplicationScoped.class)) {
74 | beanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean)).toString();
75 | }
76 | }
77 | }
78 | }
79 |
80 | private List locateStreams(AnnotatedType type) {
81 | List streams = new ArrayList<>();
82 |
83 | for (AnnotatedMethod super T> method : type.getMethods()) {
84 | if (method.getAnnotation(Incoming.class) != null || method.getAnnotation(Outgoing.class) != null) {
85 | streams.add(readStreamDescriptor(type, method));
86 | }
87 | }
88 |
89 | return streams;
90 | }
91 |
92 | private StreamDescriptor readStreamDescriptor(AnnotatedType> bean, AnnotatedMethod> method) {
93 | TypeToken returnType = TypeToken.of(method.getBaseType());
94 |
95 | // This could probably be extracted into an abstraction that is stored in a list, then we can just iterate
96 | // through the list instead of a bit if else if chain.
97 | if (returnType.isSubtypeOf(Processor.class)) {
98 | return readProcessorShape(bean, method, returnType, Processor.class, StreamShape.RS_PROCESSOR);
99 | }
100 | else if (returnType.isSubtypeOf(ProcessorBuilder.class)) {
101 | return readProcessorShape(bean, method, returnType, ProcessorBuilder.class, StreamShape.PROCESSOR_BUILDER);
102 | }
103 | else if (returnType.isSubtypeOf(Flow.class)) {
104 | return readProcessorShape(bean, method, returnType, Flow.class, StreamShape.AKKA_FLOW);
105 | }
106 | else if (returnType.isSubtypeOf(Publisher.class)) {
107 | return readPublisherShape(bean, method, returnType, Publisher.class, StreamShape.RS_PUBLISHER);
108 | }
109 | else if (returnType.isSubtypeOf(PublisherBuilder.class)) {
110 | return readPublisherShape(bean, method, returnType, PublisherBuilder.class, StreamShape.PUBLISHER_BUILDER);
111 | }
112 | else if (returnType.isSubtypeOf(Source.class)) {
113 | return readPublisherShape(bean, method, returnType, Source.class, StreamShape.AKKA_SOURCE);
114 | }
115 | else if (returnType.isSubtypeOf(Subscriber.class)) {
116 | return readSubscriberShape(bean, method, returnType, Subscriber.class, StreamShape.RS_SUBSCRIBER);
117 | }
118 | else if (returnType.isSubtypeOf(SubscriberBuilder.class)) {
119 | return readSubscriberShape(bean, method, returnType, SubscriberBuilder.class, StreamShape.SUBSCRIBER_BUILDER);
120 | }
121 | else if (returnType.isSubtypeOf(Sink.class)) {
122 | return readSubscriberShape(bean, method, returnType, Sink.class, StreamShape.AKKA_SINK);
123 | }
124 | else if (returnType.isSubtypeOf(CompletionStage.class)) {
125 |
126 | if (method.getJavaMember().getParameterCount() != 1) {
127 | throw new DefinitionException(CompletionStage.class + " returning method " + toString(method) + " must take exactly one parameter for input messages");
128 | }
129 | Type inType = method.getJavaMember().getGenericParameterTypes()[0];
130 | Type outType = Reflections.getTypeArgumentsFor(returnType, CompletionStage.class, "return type of", method)[0];
131 | return readProcessorShape(bean, method, inType, outType, StreamShape.ASYNCHRONOUS_METHOD);
132 |
133 | }
134 | else {
135 |
136 | if (method.getJavaMember().getParameterCount() != 1) {
137 | throw new DefinitionException(CompletionStage.class + " returning method " + toString(method) + " must take exactly one parameter for input messages");
138 | }
139 | Type inType = method.getJavaMember().getGenericParameterTypes()[0];
140 | Type outType = returnType.getRawType();
141 | return readProcessorShape(bean, method, inType, outType, StreamShape.SYNCHRONOUS_METHOD);
142 | }
143 | }
144 |
145 | private StreamDescriptor readProcessorShape(AnnotatedType> bean, AnnotatedMethod> method, TypeToken returnType, Class> processorClass, StreamShape shape) {
146 | if (method.getJavaMember().getParameterCount() > 0) {
147 | throw new DefinitionException(processorClass + " returning method " + toString(method) + " must not take parameters.");
148 | }
149 | Type[] processorTypes = Reflections.getTypeArgumentsFor(returnType, processorClass, "return type of", method);
150 | Type inType = processorTypes[0];
151 | Type outType = processorTypes[1];
152 | return readProcessorShape(bean, method, inType, outType, shape);
153 | }
154 |
155 | private StreamDescriptor readProcessorShape(AnnotatedType> bean, AnnotatedMethod> method, Type inType, Type outType, StreamShape shape) {
156 | Incoming incoming = method.getAnnotation(Incoming.class);
157 | if (incoming == null) {
158 | throw new DefinitionException(Outgoing.class + " annotated method " + toString(method) + " has an input type but no " + Incoming.class + " annotation.");
159 | }
160 |
161 | Outgoing outgoing = method.getAnnotation(Outgoing.class);
162 |
163 | Pair> inMessageType = unwrapMessageType(method, inType);
164 | Pair> outMessageType = unwrapMessageType(method, outType);
165 |
166 | StreamDescriptorPort incomingPort = new StreamDescriptorPort<>(incoming, inMessageType.first(), inMessageType.second());
167 | Optional> outgoingPort;
168 | boolean incomingDestinationWrapped = false;
169 | if (outgoing == null) {
170 | outgoingPort = Optional.empty();
171 | incomingDestinationWrapped = outMessageType.second().isPresent();
172 | }
173 | else {
174 | outgoingPort = Optional.of(new StreamDescriptorPort<>(outgoing, outMessageType.first(), outMessageType.second()));
175 | }
176 |
177 | return new StreamDescriptor(bean, method, Optional.of(incomingPort), outgoingPort, shape, incomingDestinationWrapped);
178 | }
179 |
180 | private StreamDescriptor readPublisherShape(AnnotatedType> bean, AnnotatedMethod> method, TypeToken returnType, Class> publisherClass, StreamShape shape) {
181 | if (method.getJavaMember().getParameterCount() > 0) {
182 | throw new DefinitionException(publisherClass + " returning method " + toString(method) + " must not take parameters.");
183 | }
184 | Type messageType = Reflections.getTypeArgumentsFor(returnType, publisherClass, "return type of", method)[0];
185 |
186 | if (method.getAnnotation(Incoming.class) != null) {
187 | throw new DefinitionException(Incoming.class + " annotated method " + toString(method) + " has no input.");
188 | }
189 |
190 | Outgoing outgoing = method.getAnnotation(Outgoing.class);
191 | Pair> unwrapped = unwrapMessageType(method, messageType);
192 | StreamDescriptorPort outgoingPort = new StreamDescriptorPort<>(outgoing, unwrapped.first(), unwrapped.second());
193 | return new StreamDescriptor(bean, method, Optional.empty(), Optional.of(outgoingPort), shape, false);
194 | }
195 |
196 | private StreamDescriptor readSubscriberShape(AnnotatedType> bean, AnnotatedMethod> method, TypeToken returnType, Class> subscriberClass, StreamShape shape) {
197 | if (method.getJavaMember().getParameterCount() > 0) {
198 | throw new DefinitionException(subscriberClass + " returning method " + toString(method) + " must not take parameters.");
199 | }
200 | Type messageType = Reflections.getTypeArgumentsFor(returnType, subscriberClass, "return type of", method)[0];
201 |
202 | if (method.getAnnotation(Outgoing.class) != null) {
203 | throw new DefinitionException(Outgoing.class + " annotated method " + toString(method) + " has no output.");
204 | }
205 |
206 | Incoming incoming = method.getAnnotation(Incoming.class);
207 | Pair> unwrapped = unwrapMessageType(method, messageType);
208 | StreamDescriptorPort incomingPort = new StreamDescriptorPort<>(incoming, unwrapped.first(), unwrapped.second());
209 | return new StreamDescriptor(bean, method, Optional.of(incomingPort), Optional.empty(), shape, false);
210 | }
211 |
212 | private Pair> unwrapMessageType(AnnotatedMethod> method, Type rawType) {
213 | TypeToken typeToken = TypeToken.of(rawType);
214 | if (typeToken.isSubtypeOf(Message.class)) {
215 | Type messageType = Reflections.getTypeArgumentsFor(typeToken, Message.class, "message type from return type of", method)[0];
216 | if (messageType instanceof Class || messageType instanceof ParameterizedType) {
217 | // todo validate parameterized type recursively to ensure it doesn't contain
218 | // abstract types
219 | return Pair.create(messageType, Optional.of(typeToken.getType()));
220 | }
221 | else {
222 | throw new DefinitionException("Could not determine message type for " + toString(method));
223 | }
224 | }
225 | else {
226 | return Pair.create(rawType, Optional.empty());
227 | }
228 | }
229 |
230 | private String toString(AnnotatedMethod> method) {
231 | return method.getDeclaringType().getJavaClass().getName() + "." + method.getJavaMember().getName();
232 | }
233 |
234 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/MessageWithCause.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.Message;
7 |
8 | import java.util.concurrent.CompletableFuture;
9 | import java.util.concurrent.CompletionStage;
10 |
11 | /**
12 | * This message implementation is used in cases where attributes from a message that caused this message, such as the
13 | * ack function, are carried with the message. It's primarily useful for debugging purposes, in particular so the
14 | * toString message can tell us which incoming message this outgoing message is associated with.
15 | */
16 | class MessageWithCause implements Message {
17 | private final Message> incomingMessage;
18 | private final Message message;
19 |
20 | MessageWithCause(Message> incomingMessage,Message message) {
21 | this.incomingMessage = incomingMessage;
22 | this.message = message;
23 | }
24 |
25 | @Override
26 | public T getPayload() {
27 | return message.getPayload();
28 | }
29 |
30 | @Override
31 | public CompletionStage ack() {
32 | return CompletableFuture.allOf(incomingMessage.ack().toCompletableFuture(),
33 | message.ack().toCompletableFuture());
34 | }
35 |
36 | @Override
37 | public String toString() {
38 | return "MessageWithCause{" +
39 | "incomingMessage=" + incomingMessage +
40 | ", message=" + message +
41 | '}';
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/PublishingStreamImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import com.lightbend.microprofile.reactive.messaging.spi.MessageSerializer;
7 | import com.lightbend.microprofile.reactive.messaging.spi.PublishingStream;
8 | import com.lightbend.microprofile.reactive.messaging.spi.SerializationSupport;
9 | import org.eclipse.microprofile.reactive.messaging.Outgoing;
10 |
11 | import javax.enterprise.inject.spi.Annotated;
12 | import java.lang.reflect.Type;
13 | import java.util.Optional;
14 |
15 | class PublishingStreamImpl implements PublishingStream {
16 |
17 | private final SerializationSupport serializationSupport;
18 | private final Outgoing outgoing;
19 | private final Type messageType;
20 | private final Annotated annotated;
21 | private final Optional wrapperType;
22 |
23 | PublishingStreamImpl(SerializationSupport serializationSupport, Outgoing outgoing, Type messageType, Annotated annotated, Optional wrapperType) {
24 | this.serializationSupport = serializationSupport;
25 | this.outgoing = outgoing;
26 | this.messageType = messageType;
27 | this.annotated = annotated;
28 | this.wrapperType = wrapperType;
29 | }
30 |
31 | @Override
32 | public Outgoing outgoing() {
33 | return outgoing;
34 | }
35 |
36 | @Override
37 | public Type messageType() {
38 | return messageType;
39 | }
40 |
41 | @Override
42 | public MessageSerializer createSerializer() {
43 | return serializationSupport.serializerFor(messageType);
44 | }
45 |
46 | @Override
47 | public Annotated annotated() {
48 | return annotated;
49 | }
50 |
51 | @Override
52 | public Optional wrapperType() {
53 | return wrapperType;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/Reflections.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import com.google.common.reflect.TypeToken;
7 |
8 | import javax.enterprise.inject.spi.AnnotatedMethod;
9 | import javax.enterprise.inject.spi.DefinitionException;
10 | import java.lang.reflect.ParameterizedType;
11 | import java.lang.reflect.Type;
12 | import java.util.Set;
13 |
14 | class Reflections {
15 | private Reflections() {}
16 |
17 | static Type[] getTypeArgumentsFor(TypeToken type, Class> classFor, String description, AnnotatedMethod> method) {
18 | if (type.getRawType().equals(classFor)) {
19 | return getTypeArguments(type, description, method);
20 | } else {
21 | // Not sure why this cast is needed, TypeSet implements Set>
22 | for (TypeToken> supertype: (Set>) type.getTypes()) {
23 | if (supertype.getRawType().equals(classFor)) {
24 | return getTypeArguments(supertype, description, method);
25 | }
26 | }
27 | }
28 | throw new DefinitionException("Cannot get the type arguments for " + type.getType() + " " + description + " " + method.getDeclaringType().getJavaClass().getName() + "." + method.getJavaMember().getName() + " due to it not implementing " + classFor);
29 | }
30 |
31 | static Type[] getTypeArguments(TypeToken type, String description, AnnotatedMethod> method) {
32 | if (type.getType() instanceof ParameterizedType) {
33 | return ((ParameterizedType) type.getType()).getActualTypeArguments();
34 | } else {
35 | throw new DefinitionException("Cannot find the type arguments for " + type + " " + description + " " + method.getDeclaringType().getJavaClass().getName() + "." + method.getJavaMember().getName() + " since it is not a parameterized type, instead, it was a " + type.getType().getClass());
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/StreamDescriptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.Incoming;
7 | import org.eclipse.microprofile.reactive.messaging.Outgoing;
8 |
9 | import javax.enterprise.inject.spi.Annotated;
10 | import javax.enterprise.inject.spi.AnnotatedMethod;
11 | import javax.enterprise.inject.spi.AnnotatedType;
12 | import java.util.Optional;
13 |
14 | class StreamDescriptor {
15 | private final AnnotatedType beanType;
16 | private final AnnotatedMethod annotated;
17 | private final Optional> incoming;
18 | private final Optional> outgoing;
19 | private final StreamShape shape;
20 | private final boolean incomingDestinationWrapped;
21 |
22 | public StreamDescriptor(AnnotatedType beanType, AnnotatedMethod annotated, Optional> incoming, Optional> outgoing, StreamShape shape, boolean incomingDestinationWrapped) {
23 | this.beanType = beanType;
24 | this.annotated = annotated;
25 | this.incoming = incoming;
26 | this.outgoing = outgoing;
27 | this.shape = shape;
28 | this.incomingDestinationWrapped = incomingDestinationWrapped;
29 | }
30 |
31 | public AnnotatedType getBeanType() {
32 | return beanType;
33 | }
34 |
35 | public AnnotatedMethod getAnnotated() {
36 | return annotated;
37 | }
38 |
39 | public Optional> getIncoming() {
40 | return incoming;
41 | }
42 |
43 | public Optional> getOutgoing() {
44 | return outgoing;
45 | }
46 |
47 | public StreamShape getShape() {
48 | return shape;
49 | }
50 |
51 | /**
52 | * This returns true if there is no outgoing destination, but the stream shape is a processor/flow and the output type
53 | * is a subclass of Message.
54 | */
55 | public boolean isIncomingDestinationWrapped() {
56 | return incomingDestinationWrapped;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/StreamDescriptorPort.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.Message;
7 |
8 | import java.lang.annotation.Annotation;
9 | import java.lang.reflect.Type;
10 | import java.util.Optional;
11 |
12 | class StreamDescriptorPort {
13 | private final T annotation;
14 | private final Type messageType;
15 | private final Optional wrapperType;
16 |
17 | public StreamDescriptorPort(T annotation, Type messageType, Optional wrapperType) {
18 | this.annotation = annotation;
19 | this.messageType = messageType;
20 | this.wrapperType = wrapperType;
21 | }
22 |
23 | public T getAnnotation() {
24 | return annotation;
25 | }
26 |
27 | public Type getMessageType() {
28 | return messageType;
29 | }
30 |
31 | public Optional getWrapperType() {
32 | return wrapperType;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/StreamManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | public interface StreamManager {
7 | StreamRunner validateStream(StreamDescriptor descriptor);
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/StreamManagerImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import akka.stream.Materializer;
7 | import com.lightbend.microprofile.reactive.messaging.jsonb.JsonbSerializationSupport;
8 | import com.lightbend.microprofile.reactive.messaging.spi.*;
9 | import com.lightbend.microprofile.reactive.streams.akka.AkkaEngine;
10 | import org.eclipse.microprofile.reactive.messaging.Incoming;
11 | import org.eclipse.microprofile.reactive.messaging.MessagingProvider;
12 | import org.eclipse.microprofile.reactive.messaging.Outgoing;
13 |
14 | import javax.enterprise.context.ApplicationScoped;
15 | import javax.enterprise.inject.Instance;
16 | import javax.enterprise.inject.spi.Annotated;
17 | import javax.enterprise.inject.spi.DeploymentException;
18 | import javax.inject.Inject;
19 | import java.util.HashMap;
20 | import java.util.Iterator;
21 | import java.util.Map;
22 |
23 | @ApplicationScoped
24 | public class StreamManagerImpl implements StreamManager {
25 | private final Map, LightbendMessagingProvider> providers = new HashMap<>();
26 | private final Materializer materializer;
27 | private final AkkaEngine akkaEngine;
28 | private final SerializationSupport serializationSupport;
29 |
30 | @Inject
31 | public StreamManagerImpl(Instance providers, Instance serializationSupport, Materializer materializer) {
32 | this.materializer = materializer;
33 | for (LightbendMessagingProvider provider: providers) {
34 | this.providers.put(provider.providerFor(), provider);
35 | }
36 | this.akkaEngine = new AkkaEngine(materializer);
37 | Iterator iter = serializationSupport.iterator();
38 | if (iter.hasNext()) {
39 | this.serializationSupport = iter.next();
40 | } else {
41 | this.serializationSupport = new JsonbSerializationSupport();
42 | }
43 | }
44 |
45 | private LightbendMessagingProvider providerFor(Class providerClass, Annotated annotated) {
46 | LightbendMessagingProvider provider = providers.get(providerClass);
47 | if (provider == null) {
48 | throw new DeploymentException("No " + LightbendMessagingProvider.class.getName() + " registered for " + providerClass.getName() + " on " + annotated);
49 | }
50 | return provider;
51 | }
52 |
53 | @Override
54 | public StreamRunner validateStream(StreamDescriptor descriptor) {
55 | ValidatedPublishingStream> validatedPublishingStream = validatePublisher(descriptor);
56 | ValidatedSubscribingStream> validatedSubscribingStream = validateSubscriber(descriptor);
57 |
58 | if (validatedPublishingStream != null && validatedSubscribingStream != null) {
59 | return new ValidatedProcessingStreamRunner<>(akkaEngine, validatedSubscribingStream, validatedPublishingStream, descriptor);
60 | } else if (validatedPublishingStream != null) {
61 | return new ValidatedPublishingStreamRunner<>(materializer, akkaEngine, validatedPublishingStream, descriptor);
62 | } else if (validatedSubscribingStream != null) {
63 | return new ValidatedSubscribingStreamRunner<>(akkaEngine, validatedSubscribingStream, descriptor);
64 | } else {
65 | throw new DeploymentException("Stream with no incoming or outcoming: " + descriptor);
66 | }
67 | }
68 |
69 | private ValidatedSubscribingStream> validateSubscriber(StreamDescriptor descriptor) {
70 | if (descriptor.getIncoming().isPresent()) {
71 | StreamDescriptorPort incoming = descriptor.getIncoming().get();
72 | LightbendMessagingProvider provider = getProvider(descriptor, incoming.getAnnotation().provider());
73 |
74 | SubscribingStream> subscribingStream = new SubscribingStreamImpl<>(serializationSupport, incoming.getAnnotation(), incoming.getMessageType(),
75 | descriptor.getAnnotated(), incoming.getWrapperType());
76 |
77 | return provider.validateSubscribingStream(subscribingStream);
78 | } else {
79 | return null;
80 | }
81 | }
82 |
83 | private ValidatedPublishingStream> validatePublisher(StreamDescriptor descriptor) {
84 | if (descriptor.getOutgoing().isPresent()) {
85 | StreamDescriptorPort outgoing = descriptor.getOutgoing().get();
86 | LightbendMessagingProvider provider = getProvider(descriptor, outgoing.getAnnotation().provider());
87 |
88 | PublishingStream> publishingStream = new PublishingStreamImpl<>(serializationSupport, outgoing.getAnnotation(), outgoing.getMessageType(),
89 | descriptor.getAnnotated(), outgoing.getWrapperType());
90 |
91 | return provider.validatePublishingStream(publishingStream);
92 | } else {
93 | return null;
94 | }
95 | }
96 |
97 | private LightbendMessagingProvider getProvider(StreamDescriptor descriptor, Class extends MessagingProvider> providerClass) {
98 | LightbendMessagingProvider provider;
99 | if (providerClass.equals(MessagingProvider.class)) {
100 | // todo - make the default provider configurable
101 | if (providers.isEmpty()) {
102 | throw new DeploymentException("No providers registered");
103 | } else {
104 | provider = providers.values().iterator().next();
105 | }
106 | } else {
107 | provider = providerFor(providerClass, descriptor.getAnnotated());
108 | }
109 | return provider;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/StreamRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import java.io.Closeable;
7 |
8 | interface StreamRunner {
9 | Closeable run(Object bean);
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/StreamShape.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | /**
7 | * The shape of the stream returned by the stream method
8 | */
9 | enum StreamShape {
10 | PUBLISHER_BUILDER,
11 | PROCESSOR_BUILDER,
12 | SUBSCRIBER_BUILDER,
13 | RS_PUBLISHER,
14 | RS_PROCESSOR,
15 | RS_SUBSCRIBER,
16 | AKKA_SOURCE,
17 | AKKA_FLOW,
18 | AKKA_SINK,
19 | ASYNCHRONOUS_METHOD,
20 | SYNCHRONOUS_METHOD
21 | }
22 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/StreamsInjectionTarget.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import javax.enterprise.context.spi.CreationalContext;
7 | import javax.enterprise.inject.spi.AnnotatedType;
8 | import javax.enterprise.inject.spi.Bean;
9 | import javax.enterprise.inject.spi.BeanManager;
10 | import javax.enterprise.inject.spi.InjectionPoint;
11 | import javax.enterprise.inject.spi.InjectionTarget;
12 | import java.io.Closeable;
13 | import java.util.ArrayList;
14 | import java.util.Collections;
15 | import java.util.IdentityHashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.Set;
19 | import java.util.concurrent.ConcurrentHashMap;
20 |
21 | class StreamsInjectionTarget implements InjectionTarget {
22 | private final BeanManager beanManager;
23 | private final AnnotatedType beanType;
24 | private final InjectionTarget wrapped;
25 | private final List descriptors;
26 | private final Map> runningStreams = new IdentityHashMap<>(new ConcurrentHashMap<>());
27 |
28 | private List validatedRunners;
29 | private List instancesToStart = new ArrayList<>();
30 |
31 | StreamsInjectionTarget(BeanManager beanManager, AnnotatedType beanType, InjectionTarget wrapped, List descriptors) {
32 | this.beanManager = beanManager;
33 | this.beanType = beanType;
34 | this.wrapped = wrapped;
35 | this.descriptors = descriptors;
36 | }
37 |
38 | void validate() {
39 | if (descriptors.isEmpty()) {
40 | validatedRunners = Collections.emptyList();
41 | }
42 | else {
43 | StreamManager streamManager = getStreamManager();
44 |
45 | List runners = new ArrayList<>();
46 | for (StreamDescriptor descriptor : descriptors) {
47 | runners.add(streamManager.validateStream(descriptor));
48 | }
49 | validatedRunners = runners;
50 | }
51 | if (!instancesToStart.isEmpty()) {
52 | List instances = instancesToStart;
53 | instancesToStart = new ArrayList<>();
54 | for (T instance : instances) {
55 | startStreams(instance);
56 | }
57 | }
58 | }
59 |
60 | AnnotatedType getBeanType() {
61 | return beanType;
62 | }
63 |
64 | private StreamManager getStreamManager() {
65 | Bean streamManagerBean = (Bean) beanManager.getBeans(StreamManager.class).iterator().next();
66 | return (StreamManager) beanManager.getReference(streamManagerBean, StreamManager.class, beanManager.createCreationalContext(streamManagerBean));
67 | }
68 |
69 | @Override
70 | public void inject(T instance, CreationalContext ctx) {
71 | wrapped.inject(instance, ctx);
72 | }
73 |
74 | @Override
75 | public void postConstruct(T instance) {
76 | wrapped.postConstruct(instance);
77 |
78 | if (validatedRunners == null) {
79 | instancesToStart.add(instance);
80 | }
81 | else {
82 | startStreams(instance);
83 | }
84 | }
85 |
86 | private void startStreams(T instance) {
87 | List streams = new ArrayList<>();
88 | try {
89 | // Start all streams
90 | validatedRunners.forEach(runner ->
91 | streams.add(runner.run(instance))
92 | );
93 | runningStreams.put(instance, streams);
94 | }
95 | catch (RuntimeException e) {
96 | stopStreams(streams);
97 | throw e;
98 | }
99 | }
100 |
101 | private void stopStreams(List streams) {
102 | streams.forEach(stream -> {
103 | try {
104 | stream.close();
105 | }
106 | catch (Exception ignored) {
107 | // todo log better
108 | ignored.printStackTrace();
109 | }
110 | });
111 | }
112 |
113 | @Override
114 | public void preDestroy(T instance) {
115 | try {
116 | List streams = runningStreams.remove(instance);
117 | if (streams != null) {
118 | stopStreams(streams);
119 | }
120 | else {
121 | // This is odd, preDestroy invoked with out a bean that was constructed?
122 | // todo log better
123 | System.out.println("preDestroy invoked on bean that hadn't been previously passed to postConstruct?");
124 | }
125 | }
126 | finally {
127 | wrapped.preDestroy(instance);
128 | }
129 | }
130 |
131 | @Override
132 | public T produce(CreationalContext ctx) {
133 | return wrapped.produce(ctx);
134 | }
135 |
136 | @Override
137 | public void dispose(T instance) {
138 | wrapped.dispose(instance);
139 | }
140 |
141 | @Override
142 | public Set getInjectionPoints() {
143 | return wrapped.getInjectionPoints();
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/SubscribingStreamImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import com.lightbend.microprofile.reactive.messaging.spi.MessageDeserializer;
7 | import com.lightbend.microprofile.reactive.messaging.spi.SerializationSupport;
8 | import com.lightbend.microprofile.reactive.messaging.spi.SubscribingStream;
9 | import org.eclipse.microprofile.reactive.messaging.Incoming;
10 |
11 | import javax.enterprise.inject.spi.Annotated;
12 | import java.lang.reflect.Type;
13 | import java.util.Optional;
14 |
15 | class SubscribingStreamImpl implements SubscribingStream {
16 |
17 | private final SerializationSupport serializationSupport;
18 | private final Incoming incoming;
19 | private final Type messageType;
20 | private final Annotated annotated;
21 | private final Optional wrapperType;
22 |
23 | SubscribingStreamImpl(SerializationSupport serializationSupport, Incoming incoming, Type messageType, Annotated annotated, Optional wrapperType) {
24 | this.serializationSupport = serializationSupport;
25 | this.incoming = incoming;
26 | this.messageType = messageType;
27 | this.annotated = annotated;
28 | this.wrapperType = wrapperType;
29 | }
30 |
31 | @Override
32 | public Incoming incoming() {
33 | return incoming;
34 | }
35 |
36 | @Override
37 | public Type messageType() {
38 | return messageType;
39 | }
40 |
41 | @Override
42 | public MessageDeserializer createDeserializer() {
43 | return serializationSupport.deserializerFor(messageType);
44 | }
45 |
46 | @Override
47 | public Annotated annotated() {
48 | return annotated;
49 | }
50 |
51 | @Override
52 | public Optional wrapperType() {
53 | return wrapperType;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/ValidatedProcessingStreamRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import akka.NotUsed;
7 | import akka.stream.javadsl.Flow;
8 | import akka.stream.javadsl.Keep;
9 | import com.lightbend.microprofile.reactive.messaging.spi.ValidatedPublishingStream;
10 | import com.lightbend.microprofile.reactive.messaging.spi.ValidatedSubscribingStream;
11 | import com.lightbend.microprofile.reactive.streams.akka.AkkaEngine;
12 | import org.eclipse.microprofile.reactive.messaging.Message;
13 |
14 | import java.io.Closeable;
15 | import java.lang.reflect.InvocationTargetException;
16 | import java.util.concurrent.CompletionStage;
17 |
18 | class ValidatedProcessingStreamRunner implements StreamRunner {
19 |
20 | private static final int SINGLE_METHOD_PARALLELISM = 1;
21 |
22 | private final AkkaEngine akkaEngine;
23 | private final ValidatedSubscribingStream subscribingStream;
24 | private final ValidatedPublishingStream publishingStream;
25 | private final StreamDescriptor descriptor;
26 |
27 |
28 | ValidatedProcessingStreamRunner(AkkaEngine akkaEngine, ValidatedSubscribingStream subscribingStream,
29 | ValidatedPublishingStream publishingStream, StreamDescriptor descriptor) {
30 | this.akkaEngine = akkaEngine;
31 | this.subscribingStream = subscribingStream;
32 | this.publishingStream = publishingStream;
33 | this.descriptor = descriptor;
34 | }
35 |
36 | public Closeable run(Object bean) {
37 | return subscribingStream.runFlow(() -> create(bean));
38 | }
39 |
40 | private Flow, Message>, NotUsed> create(Object bean) throws Exception {
41 |
42 | Flow rawFlow;
43 | switch(descriptor.getShape()) {
44 | case PROCESSOR_BUILDER:
45 | rawFlow = akkaEngine.buildFlow(invokeMethod(bean));
46 | break;
47 | case RS_PROCESSOR:
48 | rawFlow = Flow.fromProcessor(() -> invokeMethod(bean));
49 | break;
50 | case AKKA_FLOW:
51 | rawFlow = invokeMethod(bean);
52 | break;
53 | case ASYNCHRONOUS_METHOD:
54 | rawFlow = Flow.create().mapAsync(SINGLE_METHOD_PARALLELISM, t -> (CompletionStage) invokeMethod(bean, t));
55 | break;
56 | case SYNCHRONOUS_METHOD:
57 | rawFlow = Flow.create().map(t -> invokeMethod(bean, t));
58 | break;
59 | default:
60 | throw new RuntimeException("Invalid shape for processor: " + descriptor.getShape());
61 | }
62 |
63 | Flow, Message>, NotUsed> consumer = publishingStream.getFlowConsumer();
64 |
65 | if (wrappedIncoming() && wrappedOutgoing()) {
66 | return ((Flow, Message, ?>) rawFlow).viaMat(consumer, Keep.right());
67 | } else {
68 |
69 | Flow, Message, ?> flow;
70 | if (wrappedIncoming()) {
71 | flow = ((Flow, R, ?>) rawFlow)
72 | .map(Message::of);
73 | } else if (wrappedOutgoing()) {
74 | flow = Flow.>create()
75 | .map(Message::getPayload)
76 | .via((Flow, ?>) rawFlow);
77 | } else {
78 | flow = Flow.>create()
79 | .map(Message::getPayload)
80 | .via((Flow) rawFlow)
81 | .map(Message::of);
82 | }
83 |
84 | return FlowUtils.bypassFlow(flow).viaMat(consumer, Keep.right());
85 | }
86 | }
87 |
88 | private boolean wrappedIncoming() {
89 | return descriptor.getIncoming().get().getWrapperType().isPresent();
90 | }
91 |
92 | private boolean wrappedOutgoing() {
93 | return descriptor.getOutgoing().get().getWrapperType().isPresent();
94 | }
95 |
96 | private S invokeMethod(Object bean, Object... args) throws Exception {
97 | try {
98 | return (S) descriptor.getAnnotated().getJavaMember().invoke(bean, args);
99 | } catch (InvocationTargetException ite) {
100 | if (ite.getCause() instanceof Exception) {
101 | throw (Exception) ite.getCause();
102 | } else if (ite.getCause() instanceof Error) {
103 | throw (Error) ite.getCause();
104 | } else {
105 | throw ite;
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/ValidatedPublishingStreamRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import akka.Done;
7 | import akka.NotUsed;
8 | import akka.stream.KillSwitch;
9 | import akka.stream.KillSwitches;
10 | import akka.stream.Materializer;
11 | import akka.stream.javadsl.Flow;
12 | import akka.stream.javadsl.Keep;
13 | import akka.stream.javadsl.RestartSource;
14 | import akka.stream.javadsl.Sink;
15 | import akka.stream.javadsl.Source;
16 | import com.lightbend.microprofile.reactive.messaging.spi.ValidatedPublishingStream;
17 | import com.lightbend.microprofile.reactive.streams.akka.AkkaEngine;
18 | import org.eclipse.microprofile.reactive.messaging.Message;
19 | import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder;
20 | import org.reactivestreams.Publisher;
21 |
22 | import java.io.Closeable;
23 | import java.time.Duration;
24 | import java.util.concurrent.CompletableFuture;
25 |
26 | class ValidatedPublishingStreamRunner implements StreamRunner {
27 |
28 | private final Materializer materializer;
29 | private final AkkaEngine akkaEngine;
30 | private final ValidatedPublishingStream publishingStream;
31 | private final StreamDescriptor descriptor;
32 |
33 | ValidatedPublishingStreamRunner(Materializer materializer, AkkaEngine akkaEngine, ValidatedPublishingStream publishingStream,
34 | StreamDescriptor descriptor) {
35 | this.materializer = materializer;
36 | this.akkaEngine = akkaEngine;
37 | this.publishingStream = publishingStream;
38 | this.descriptor = descriptor;
39 | }
40 |
41 | private static final long MIN_BACKOFF_MS = 3000;
42 | private static final long MAX_BACKOFF_MS = 30000;
43 | private static final double RANDOM_FACTOR = 0.2;
44 | private static final int MAX_RESTARTS = -1;
45 | private static final int ACK_PARALLELISM = 1;
46 |
47 | public Closeable run(Object bean) {
48 |
49 | Source restartSource = RestartSource.onFailuresWithBackoff(
50 | Duration.ofMillis(MIN_BACKOFF_MS),
51 | Duration.ofMillis(MAX_BACKOFF_MS),
52 | RANDOM_FACTOR,
53 | MAX_RESTARTS,
54 | () -> {
55 | Object stream = descriptor.getAnnotated().getJavaMember().invoke(bean);
56 | Source rawSource;
57 | switch (descriptor.getShape()) {
58 | case PUBLISHER_BUILDER:
59 | rawSource = akkaEngine.buildSource((PublisherBuilder) stream);
60 | break;
61 | case RS_PUBLISHER:
62 | rawSource = Source.fromPublisher((Publisher) stream);
63 | break;
64 | case AKKA_SOURCE:
65 | rawSource = (Source) stream;
66 | break;
67 | default:
68 | throw new RuntimeException("Invalid shape for publisher: " + descriptor.getShape());
69 | }
70 |
71 | Flow, Message>, NotUsed> consumer = publishingStream.getFlowConsumer();
72 |
73 | if (descriptor.getOutgoing().get().getWrapperType().isPresent()) {
74 | Source, ?> source = rawSource;
75 | return source.via(consumer)
76 | .mapAsync(ACK_PARALLELISM, m ->
77 | m.ack().thenApply(v -> Done.getInstance()));
78 | } else {
79 | // Since the published messages don't need to be acked, we just wrap them in a dummy message, and don't worry about
80 | // acking it.
81 | return rawSource.map(payload -> Message.of(payload))
82 | .via(consumer)
83 | .map(m -> Done.getInstance());
84 | }
85 | }
86 | );
87 |
88 | KillSwitch killSwitch = restartSource.viaMat(KillSwitches.single(), Keep.right())
89 | .to(Sink.ignore())
90 | .run(materializer);
91 |
92 | return killSwitch::shutdown;
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/impl/ValidatedSubscribingStreamRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.impl;
5 |
6 | import akka.Done;
7 | import akka.NotUsed;
8 | import akka.stream.javadsl.Flow;
9 | import akka.stream.javadsl.Sink;
10 | import com.lightbend.microprofile.reactive.messaging.spi.ValidatedSubscribingStream;
11 | import com.lightbend.microprofile.reactive.streams.akka.AkkaEngine;
12 | import org.eclipse.microprofile.reactive.messaging.Message;
13 |
14 | import java.io.Closeable;
15 | import java.lang.reflect.InvocationTargetException;
16 | import java.util.concurrent.CompletionStage;
17 |
18 | class ValidatedSubscribingStreamRunner implements StreamRunner {
19 |
20 | private static final int SINGLE_METHOD_PARALLELISM = 1;
21 | private static final int AUTO_ACK_PARALLELISM = 4;
22 |
23 | private final AkkaEngine akkaEngine;
24 | private final ValidatedSubscribingStream subscribingStream;
25 | private final StreamDescriptor descriptor;
26 |
27 | ValidatedSubscribingStreamRunner(AkkaEngine akkaEngine, ValidatedSubscribingStream subscribingStream,
28 | StreamDescriptor descriptor) {
29 | this.akkaEngine = akkaEngine;
30 | this.subscribingStream = subscribingStream;
31 | this.descriptor = descriptor;
32 | }
33 |
34 | public Closeable run(Object bean) {
35 | switch (descriptor.getShape()) {
36 | case PROCESSOR_BUILDER:
37 | case RS_PROCESSOR:
38 | case AKKA_FLOW:
39 | case ASYNCHRONOUS_METHOD:
40 | case SYNCHRONOUS_METHOD:
41 | return subscribingStream.runFlow(() -> createFlow(bean));
42 | case SUBSCRIBER_BUILDER:
43 | case RS_SUBSCRIBER:
44 | case AKKA_SINK:
45 | return subscribingStream.runSink(() -> createSink(bean));
46 | default:
47 | throw new RuntimeException("Invalid shape for subscriber: " + descriptor.getShape());
48 | }
49 | }
50 |
51 | private Flow, Message>, NotUsed> createFlow(Object bean) throws Exception {
52 |
53 | Flow rawFlow;
54 | switch(descriptor.getShape()) {
55 | case PROCESSOR_BUILDER:
56 | rawFlow = akkaEngine.buildFlow(invokeMethod(bean));
57 | break;
58 | case RS_PROCESSOR:
59 | rawFlow = Flow.fromProcessor(() -> invokeMethod(bean));
60 | break;
61 | case AKKA_FLOW:
62 | rawFlow = invokeMethod(bean);
63 | break;
64 | case ASYNCHRONOUS_METHOD:
65 | rawFlow = Flow.create().mapAsync(SINGLE_METHOD_PARALLELISM, t ->
66 | ((CompletionStage) invokeMethod(bean, t)).thenApply(r -> {
67 | if (r == null) {
68 | return Done.getInstance();
69 | } else {
70 | return r;
71 | }
72 | })
73 | );
74 | break;
75 | case SYNCHRONOUS_METHOD:
76 | rawFlow = Flow.create().map(t -> {
77 | Object result = invokeMethod(bean, t);
78 | if (result == null) {
79 | return Done.getInstance();
80 | } else {
81 | return result;
82 | }
83 | });
84 | break;
85 | default:
86 | throw new RuntimeException("Invalid shape for flow subscriber: " + descriptor.getShape());
87 | }
88 |
89 | if (wrappedIncoming() && descriptor.isIncomingDestinationWrapped()) {
90 | return ((Flow, Message>, ?>) rawFlow).mapMaterializedValue(v -> NotUsed.getInstance());
91 | } else {
92 |
93 | Flow, Message>, ?> flow;
94 | if (wrappedIncoming()) {
95 | return (Flow) ((Flow, ?, ?>) rawFlow)
96 | .map(Message::of);
97 | } else if (descriptor.isIncomingDestinationWrapped()) {
98 | flow = Flow.>create()
99 | .map(Message::getPayload)
100 | .via((Flow, ?>) rawFlow);
101 | } else {
102 | flow = Flow.>create()
103 | .map(Message::getPayload)
104 | .via((Flow) rawFlow)
105 | .map(Message::of);
106 | }
107 |
108 | return FlowUtils.bypassFlow((Flow) flow);
109 | }
110 | }
111 |
112 | private Sink, NotUsed> createSink(Object bean) throws Exception {
113 |
114 | Sink rawSink;
115 | switch(descriptor.getShape()) {
116 | case SUBSCRIBER_BUILDER:
117 | rawSink = akkaEngine.buildSink(invokeMethod(bean));
118 | break;
119 | case RS_SUBSCRIBER:
120 | rawSink = Sink.fromSubscriber(invokeMethod(bean));
121 | break;
122 | case AKKA_SINK:
123 | rawSink = invokeMethod(bean);
124 | break;
125 | default:
126 | throw new RuntimeException("Invalid shape for sink subscriber: " + descriptor.getShape());
127 | }
128 |
129 | if (wrappedIncoming()) {
130 | return ((Sink, ?>) rawSink).mapMaterializedValue(v -> NotUsed.getInstance());
131 | } else {
132 | return Flow.>create()
133 | .mapAsync(AUTO_ACK_PARALLELISM, message -> message.ack().thenApply(v -> message.getPayload()))
134 | .to(rawSink);
135 | }
136 | }
137 |
138 | private boolean wrappedIncoming() {
139 | return descriptor.getIncoming().get().getWrapperType().isPresent();
140 | }
141 |
142 | private S invokeMethod(Object bean, Object... args) throws Exception {
143 | try {
144 | return (S) descriptor.getAnnotated().getJavaMember().invoke(bean, args);
145 | } catch (InvocationTargetException ite) {
146 | if (ite.getCause() instanceof Exception) {
147 | throw (Exception) ite.getCause();
148 | } else if (ite.getCause() instanceof Error) {
149 | throw (Error) ite.getCause();
150 | } else {
151 | throw ite;
152 | }
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/jsonb/JsonbMessageDeserializer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.jsonb;
5 |
6 | import com.lightbend.microprofile.reactive.messaging.spi.MessageDeserializer;
7 |
8 | import javax.json.bind.Jsonb;
9 | import java.io.ByteArrayInputStream;
10 | import java.lang.reflect.Type;
11 |
12 | public class JsonbMessageDeserializer implements MessageDeserializer {
13 | private final Type type;
14 | private final Jsonb jsonb;
15 |
16 | public JsonbMessageDeserializer(Type type, Jsonb jsonb) {
17 | this.type = type;
18 | this.jsonb = jsonb;
19 | }
20 |
21 | @Override
22 | public T fromBytes(byte[] bytes) {
23 | return jsonb.fromJson(new ByteArrayInputStream(bytes), type);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/jsonb/JsonbMessageSerializer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.jsonb;
5 |
6 | import com.lightbend.microprofile.reactive.messaging.spi.MessageSerializer;
7 |
8 | import javax.json.bind.Jsonb;
9 | import java.io.ByteArrayOutputStream;
10 | import java.lang.reflect.Type;
11 |
12 | public class JsonbMessageSerializer implements MessageSerializer {
13 | private final Type type;
14 | private final Jsonb jsonb;
15 |
16 | public JsonbMessageSerializer(Type type, Jsonb jsonb) {
17 | this.type = type;
18 | this.jsonb = jsonb;
19 | }
20 |
21 | @Override
22 | public byte[] toBytes(T message) {
23 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
24 | jsonb.toJson(message, type, baos);
25 | return baos.toByteArray();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/jsonb/JsonbSerializationSupport.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.jsonb;
5 |
6 | import com.lightbend.microprofile.reactive.messaging.spi.MessageDeserializer;
7 | import com.lightbend.microprofile.reactive.messaging.spi.MessageSerializer;
8 | import com.lightbend.microprofile.reactive.messaging.spi.SerializationSupport;
9 |
10 | import javax.json.bind.Jsonb;
11 | import javax.json.bind.JsonbBuilder;
12 | import java.lang.reflect.Type;
13 |
14 | public class JsonbSerializationSupport implements SerializationSupport {
15 | private final Jsonb jsonb = JsonbBuilder.create();
16 |
17 | @Override
18 | public MessageSerializer serializerFor(Type type) {
19 | return new JsonbMessageSerializer<>(type, jsonb);
20 | }
21 |
22 | @Override
23 | public MessageDeserializer deserializerFor(Type type) {
24 | return new JsonbMessageDeserializer<>(type, jsonb);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/spi/LightbendMessagingProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.spi;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.MessagingProvider;
7 |
8 | /**
9 | * A messaging provider for Lightbend messaging.
10 | *
11 | * Implementations of this class must register themselves as a
12 | * {@link javax.enterprise.context.ApplicationScoped} bean.
13 | */
14 | public interface LightbendMessagingProvider {
15 |
16 | /**
17 | * The messaging provider marker that this provider is for.
18 | */
19 | Class extends MessagingProvider> providerFor();
20 |
21 | /**
22 | * Validate the given publishing stream.
23 | */
24 | ValidatedPublishingStream validatePublishingStream(PublishingStream stream);
25 |
26 | /**
27 | * Validate the given subscribing stream.
28 | */
29 | ValidatedSubscribingStream validateSubscribingStream(SubscribingStream stream);
30 | }
31 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/spi/MessageDeserializer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.spi;
5 |
6 | /**
7 | * A message deserializer.
8 | *
9 | * This will be moved in some form to the messaging spec, it exists here now as a placeholder.
10 | *
11 | * @param
12 | */
13 | public interface MessageDeserializer {
14 | T fromBytes(byte[] bytes);
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/spi/MessageSerializer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.spi;
5 |
6 | /**
7 | * A message serializer.
8 | *
9 | * This will be moved in some form to the messaging spec, it exists here now as a placeholder.
10 | *
11 | * @param
12 | */
13 | public interface MessageSerializer {
14 | byte[] toBytes(T message);
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/spi/PublishingStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.spi;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.Outgoing;
7 |
8 | import javax.enterprise.inject.spi.Annotated;
9 | import java.lang.reflect.Type;
10 | import java.util.Optional;
11 |
12 | /**
13 | * Descriptor for a publishing stream.
14 | */
15 | public interface PublishingStream {
16 |
17 | /**
18 | * The outgoing annotation.
19 | */
20 | Outgoing outgoing();
21 |
22 | /**
23 | * The type of message being subscribed to - ie T.
24 | */
25 | Type messageType();
26 |
27 | /**
28 | * The wrapper type of the message, if it's wrapped.
29 | *
30 | * This allows message providers to subtype {@link org.eclipse.microprofile.reactive.messaging.Message} for
31 | * the purpose of allowing users to attach extra meta data to messages that they are publishing.
32 | *
33 | * This must be a subtype of {@link org.eclipse.microprofile.reactive.messaging.Message}.
34 | *
35 | * The messaging provider must validate that it supports publishing messages of this type.
36 | */
37 | Optional wrapperType();
38 |
39 | /**
40 | * Invoke if a serializer is needed.
41 | *
42 | * Some providers handle message serialization internally, the configured serializer only needs to be used if not.
43 | */
44 | MessageSerializer createSerializer();
45 |
46 | /**
47 | * The annotated element that represents the stream, allows the provider to read additional meta-data from it.
48 | */
49 | Annotated annotated();
50 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/spi/SerializationSupport.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.spi;
5 |
6 | import java.lang.reflect.Type;
7 |
8 | public interface SerializationSupport {
9 | MessageSerializer serializerFor(Type type);
10 | MessageDeserializer deserializerFor(Type type);
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/spi/SubscribingStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.spi;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.Incoming;
7 |
8 | import javax.enterprise.inject.spi.Annotated;
9 | import java.lang.reflect.Type;
10 | import java.util.Optional;
11 |
12 | /**
13 | * Descriptor for a subscribing stream.
14 | */
15 | public interface SubscribingStream {
16 |
17 | /**
18 | * The incoming annotation
19 | */
20 | Incoming incoming();
21 |
22 | /**
23 | * The type of message being subscribed to - ie T.
24 | */
25 | Type messageType();
26 |
27 | /**
28 | * The wrapper type of the message, if it's wrapped.
29 | *
30 | * This allows message providers to subtype {@link org.eclipse.microprofile.reactive.messaging.Message} for
31 | * the purpose of allowing users to read extra meta data to messages that they are subscribing to.
32 | *
33 | * This must be a subtype of {@link org.eclipse.microprofile.reactive.messaging.Message}.
34 | *
35 | * The messaging provider must validate that it supports subscribing to messages of this type, and, only emit
36 | * messages of this type to the stream.
37 | */
38 | Optional wrapperType();
39 |
40 | /**
41 | * Invoke if a deserializer is needed.
42 | *
43 | * Some brokers handle message deserialization internally, the configured deserializer only needs to be used if not.
44 | */
45 | MessageDeserializer createDeserializer();
46 |
47 | /**
48 | * The annotated element that represents the stream, allows the provider to read additional meta-data from it.
49 | */
50 | Annotated annotated();
51 | }
52 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/spi/ValidatedPublishingStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.spi;
5 |
6 | import akka.NotUsed;
7 | import akka.stream.javadsl.Flow;
8 | import org.eclipse.microprofile.reactive.messaging.Message;
9 |
10 | /**
11 | * Returned by a {@link LightbendMessagingProvider} to get a subscriber to consume messages to be published.
12 | */
13 | public interface ValidatedPublishingStream {
14 | /**
15 | * Get the consumer for this stream.
16 | */
17 | Flow, Message>, NotUsed> getFlowConsumer();
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lightbend/microprofile/reactive/messaging/spi/ValidatedSubscribingStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.spi;
5 |
6 | import akka.NotUsed;
7 | import akka.japi.function.Creator;
8 | import akka.stream.KillSwitch;
9 | import akka.stream.javadsl.Flow;
10 | import akka.stream.javadsl.Sink;
11 | import org.eclipse.microprofile.reactive.messaging.Message;
12 |
13 | import java.io.Closeable;
14 |
15 | /**
16 | * Returned by a {@link LightbendMessagingProvider} to allow the stream to be run.
17 | */
18 | public interface ValidatedSubscribingStream {
19 |
20 | /**
21 | * Run the stream with the given flow.
22 | *
23 | * It is the responsibility of this method to run the subscriber and keep it running when failures occur. It's also
24 | * the responsibility of this method to acknowledge the outgoing messages from this stream. They may or may not be
25 | * correlated with the incoming messages.
26 | *
27 | * When the kill switch is shutdown, the stream must be shut down.
28 | */
29 | Closeable runFlow(Creator, Message>, NotUsed>> createConsumer);
30 |
31 | /**
32 | * Run the stream with the given sink.
33 | */
34 | Closeable runSink(Creator, NotUsed>> createConsumer);
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension:
--------------------------------------------------------------------------------
1 | com.lightbend.microprofile.reactive.messaging.impl.LightbendReactiveMessagingCdiExtension
--------------------------------------------------------------------------------
/example/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 | 4.0.0
8 |
9 |
10 | com.lightbend.microprofile.reactive.messaging
11 | lightbend-microprofile-reactive-messaging-parent
12 | 1.0-SNAPSHOT
13 |
14 |
15 | ${project.parent.groupId}lightbend-microprofile-reactive-messaging-examples
16 | Lightbend MicroProfile Reactive Messaging Examples
17 | Lightbend MicroProfile Reactive Messaging :: Examples
18 |
19 |
20 | ${project.groupId}
21 | lightbend-microprofile-reactive-messaging-kafka
22 | ${project.version}
23 |
24 |
25 | ${project.groupId}
26 | lightbend-microprofile-reactive-messaging-akka
27 | ${project.version}
28 |
29 |
30 | org.jboss.weld.se
31 | weld-se-core
32 |
33 |
34 | org.glassfish
35 | javax.json
36 |
37 |
38 | org.eclipse
39 | yasson
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | org.apache.maven.plugins
48 | maven-javadoc-plugin
49 |
50 |
51 | attach-javadocs
52 |
53 | jar
54 |
55 |
56 |
57 |
58 |
59 | org.apache.maven.plugins
60 | maven-source-plugin
61 |
62 |
63 | attach-sources
64 |
65 | jar
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/example/src/main/java/com/example/ActorSystemProducer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.example;
5 |
6 | import akka.actor.ActorSystem;
7 | import akka.stream.ActorMaterializer;
8 | import akka.stream.Materializer;
9 |
10 | import javax.annotation.PreDestroy;
11 | import javax.enterprise.context.ApplicationScoped;
12 | import javax.enterprise.inject.Produces;
13 |
14 | @ApplicationScoped
15 | public class ActorSystemProducer {
16 |
17 | private final ActorSystem system = ActorSystem.create();
18 | private final Materializer materializer = ActorMaterializer.create(system);
19 |
20 | @Produces
21 | public ActorSystem getSystem() {
22 | return system;
23 | }
24 |
25 | @Produces
26 | public Materializer getMaterializer() {
27 | return materializer;
28 | }
29 |
30 | @PreDestroy
31 | public void shutdown() {
32 | system.terminate();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/example/src/main/java/com/example/Application.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.example;
5 |
6 | import org.jboss.weld.environment.se.Weld;
7 | import org.jboss.weld.environment.se.WeldContainer;
8 |
9 | import java.net.URL;
10 | import java.util.Enumeration;
11 |
12 | public class Application {
13 | public static void main(String... args) throws Exception {
14 |
15 | Enumeration beans = Application.class.getClassLoader().getResources("META-INF/beans.xml");
16 | while(beans.hasMoreElements()) {
17 | System.out.println(beans.nextElement());
18 | }
19 |
20 | Weld weld = new Weld();
21 | weld.setClassLoader(Application.class.getClassLoader());
22 | WeldContainer container = weld.initialize();
23 |
24 | System.out.println("Press any key to shutdown...");
25 |
26 | System.in.read();
27 |
28 | System.out.println("Shutting down...");
29 |
30 | container.close();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example/src/main/java/com/example/MyMessageHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.example;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.Incoming;
7 | import org.eclipse.microprofile.reactive.messaging.Outgoing;
8 | import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder;
9 | import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams;
10 |
11 | import javax.enterprise.context.ApplicationScoped;
12 |
13 | @ApplicationScoped
14 | public class MyMessageHandler {
15 |
16 | @Incoming("my.messages.in1")
17 | @Outgoing("my.messages.out1")
18 | public ProcessorBuilder processMessages() {
19 | return ReactiveStreams.builder()
20 | .map(msg -> {
21 | System.out.println("Got this message: " + msg);
22 | return "processed " + msg;
23 | });
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/src/main/resources/META-INF/beans.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
11 |
--------------------------------------------------------------------------------
/kafka-tck/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 | 4.0.0
8 |
9 |
10 | com.lightbend.microprofile.reactive.messaging
11 | lightbend-microprofile-reactive-messaging-parent
12 | 1.0-SNAPSHOT
13 |
14 |
15 | lightbend-microprofile-reactive-messaging-kafka-tck
16 | Lightbend MicroProfile Reactive Messaging Kafka TCK
17 | Lightbend MicroProfile Reactive Messaging :: Kafka TCK tests
18 |
19 |
20 | ${project.groupId}
21 | lightbend-microprofile-reactive-messaging-kafka
22 | ${project.version}
23 |
24 |
25 |
26 |
27 | org.eclipse.microprofile.reactive.messaging
28 | microprofile-reactive-messaging-tck
29 | provided
30 |
31 |
32 | ${project.groupId}
33 | lightbend-microprofile-reactive-messaging-akka
34 | ${project.version}
35 |
36 |
37 | org.jboss.weld.se
38 | weld-se-core
39 |
40 |
41 | org.jboss.arquillian.container
42 | arquillian-weld-embedded
43 |
44 |
45 | org.testng
46 | testng
47 |
48 |
49 | org.glassfish
50 | javax.json
51 |
52 |
53 | org.eclipse
54 | yasson
55 |
56 |
57 | com.typesafe.akka
58 | akka-slf4j_2.12
59 |
60 |
61 | org.slf4j
62 | slf4j-log4j12
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | org.apache.maven.plugins
71 | maven-javadoc-plugin
72 |
73 |
74 | attach-javadocs
75 |
76 | jar
77 |
78 |
79 |
80 |
81 |
82 | org.apache.maven.plugins
83 | maven-source-plugin
84 |
85 |
86 | attach-sources
87 |
88 | jar
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/kafka-tck/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/tck/client/KafkaAdminClientListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka.tck.client;
5 |
6 | import org.apache.kafka.clients.admin.AdminClient;
7 | import org.apache.kafka.clients.admin.AdminClientConfig;
8 | import org.jboss.arquillian.container.spi.event.container.AfterStart;
9 | import org.jboss.arquillian.container.spi.event.container.AfterStop;
10 | import org.jboss.arquillian.core.api.InstanceProducer;
11 | import org.jboss.arquillian.core.api.annotation.ApplicationScoped;
12 | import org.jboss.arquillian.core.api.annotation.Inject;
13 | import org.jboss.arquillian.core.api.annotation.Observes;
14 |
15 | import java.util.Properties;
16 |
17 | public class KafkaAdminClientListener {
18 |
19 | @ApplicationScoped
20 | @Inject
21 | private InstanceProducer adminClient;
22 |
23 | public void onAfterStart(@Observes AfterStart afterStart) {
24 | Properties properties = new Properties();
25 | properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
26 |
27 | adminClient.set(AdminClient.create(properties));
28 | }
29 |
30 | public void onAfterStop(@Observes AfterStop afterStop) {
31 | adminClient.get().close();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/kafka-tck/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/tck/client/KafkaArquillianTckExtension.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka.tck.client;
5 |
6 | import org.jboss.arquillian.container.test.spi.client.deployment.AuxiliaryArchiveAppender;
7 | import org.jboss.arquillian.core.spi.LoadableExtension;
8 |
9 | public class KafkaArquillianTckExtension implements LoadableExtension {
10 |
11 | @Override
12 | public void register(ExtensionBuilder builder) {
13 | builder.observer(KafkaAdminClientListener.class)
14 | .observer(KafkaTopicDeployListener.class)
15 | .service(AuxiliaryArchiveAppender.class, KafkaTckAuxilleryArchiveAppender.class);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/kafka-tck/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/tck/client/KafkaTckAuxilleryArchiveAppender.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka.tck.client;
5 |
6 | import com.lightbend.microprofile.reactive.messaging.kafka.tck.container.KafkaArquillianTckRemoteExtension;
7 | import org.jboss.arquillian.container.test.spi.RemoteLoadableExtension;
8 | import org.jboss.arquillian.container.test.spi.client.deployment.AuxiliaryArchiveAppender;
9 | import org.jboss.shrinkwrap.api.Archive;
10 | import org.jboss.shrinkwrap.api.ShrinkWrap;
11 | import org.jboss.shrinkwrap.api.spec.JavaArchive;
12 |
13 | public class KafkaTckAuxilleryArchiveAppender implements AuxiliaryArchiveAppender {
14 |
15 | @Override
16 | public Archive> createAuxiliaryArchive() {
17 | return ShrinkWrap.create(JavaArchive.class)
18 | .addPackage(KafkaArquillianTckRemoteExtension.class.getPackage())
19 | .addAsServiceProvider(RemoteLoadableExtension.class, KafkaArquillianTckRemoteExtension.class);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/kafka-tck/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/tck/client/KafkaTopicDeployListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka.tck.client;
5 |
6 | import org.apache.kafka.clients.admin.AdminClient;
7 | import org.apache.kafka.clients.admin.NewTopic;
8 | import org.eclipse.microprofile.reactive.messaging.tck.client.event.DeployTopics;
9 | import org.eclipse.microprofile.reactive.messaging.tck.client.event.UnDeployTopics;
10 | import org.eclipse.microprofile.reactive.messaging.tck.container.TestEnvironment;
11 | import org.jboss.arquillian.core.api.Instance;
12 | import org.jboss.arquillian.core.api.annotation.Inject;
13 | import org.jboss.arquillian.core.api.annotation.Observes;
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 |
17 | import java.util.ArrayList;
18 | import java.util.HashSet;
19 | import java.util.List;
20 | import java.util.Set;
21 | import java.util.concurrent.ExecutionException;
22 | import java.util.concurrent.TimeUnit;
23 | import java.util.concurrent.TimeoutException;
24 |
25 | public class KafkaTopicDeployListener {
26 |
27 | private static final Logger log = LoggerFactory.getLogger(KafkaTopicDeployListener.class);
28 |
29 | @Inject
30 | private Instance adminClient;
31 | @Inject
32 | private Instance testEnvironment;
33 |
34 | public void onDeployTopics(@Observes DeployTopics deployTopics) throws ExecutionException, InterruptedException, TimeoutException {
35 | List topics = deployTopics.getTopics();
36 | AdminClient client = adminClient.get();
37 |
38 | if (topics.size() > 0) {
39 | Set toDelete = new HashSet<>(topics);
40 | toDelete.retainAll(client.listTopics().names().get());
41 | if (!toDelete.isEmpty()) {
42 | log.debug("Deleting existing topics: " + String.join(", ", toDelete));
43 | client.deleteTopics(toDelete).all().get(testEnvironment.get().receiveTimeout().toMillis(), TimeUnit.MILLISECONDS);
44 | log.debug("Waiting for topics to be deleted...");
45 | Thread.sleep(1000);
46 | }
47 |
48 | List newTopics = new ArrayList<>();
49 | for (String topic : topics) {
50 | newTopics.add(new NewTopic(topic, 1, (short) 1));
51 | }
52 |
53 | log.debug("Creating topics: " + String.join(", ", topics));
54 | client.createTopics(newTopics).all().get();
55 | }
56 | }
57 |
58 | public void onUnDeployTopics(@Observes UnDeployTopics unDeployTopics) throws ExecutionException, InterruptedException {
59 | List topics = unDeployTopics.getTopics();
60 |
61 | if (topics.size() > 0) {
62 | log.debug("Tearing down topics: " + String.join(", ", topics));
63 | adminClient.get().deleteTopics(topics).all().get();
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/kafka-tck/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/tck/container/KafkaArquillianTckRemoteExtension.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka.tck.container;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.tck.container.TckMessagingPuppet;
7 | import org.jboss.arquillian.container.test.spi.RemoteLoadableExtension;
8 |
9 | public class KafkaArquillianTckRemoteExtension implements RemoteLoadableExtension {
10 |
11 | @Override
12 | public void register(ExtensionBuilder builder) {
13 | builder.observer(KafkaContainerListener.class)
14 | .service(TckMessagingPuppet.class, KafkaTckMessagingPuppet.class);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/kafka-tck/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/tck/container/KafkaContainerListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka.tck.container;
5 |
6 | import akka.actor.ActorSystem;
7 | import akka.stream.ActorMaterializer;
8 | import akka.stream.Materializer;
9 | import org.jboss.arquillian.core.api.InstanceProducer;
10 | import org.jboss.arquillian.core.api.annotation.ApplicationScoped;
11 | import org.jboss.arquillian.core.api.annotation.Inject;
12 | import org.jboss.arquillian.core.api.annotation.Observes;
13 | import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
14 | import org.jboss.arquillian.test.spi.event.suite.BeforeSuite;
15 |
16 | public class KafkaContainerListener {
17 |
18 | @ApplicationScoped
19 | @Inject
20 | private InstanceProducer actorSystem;
21 | @ApplicationScoped
22 | @Inject
23 | private InstanceProducer materializer;
24 |
25 | public void onBeforeSuite(@Observes BeforeSuite beforeSuite) {
26 | ActorSystem system = ActorSystem.create();
27 | actorSystem.set(system);
28 | materializer.set(ActorMaterializer.create(system));
29 | }
30 |
31 | public void onAfterSuite(@Observes AfterSuite afterSuite) {
32 | actorSystem.get().terminate();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/kafka-tck/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/tck/container/KafkaTckMessagingPuppet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka.tck.container;
5 |
6 | import akka.actor.ActorSystem;
7 | import akka.kafka.ConsumerSettings;
8 | import akka.kafka.ProducerSettings;
9 | import akka.kafka.Subscriptions;
10 | import akka.kafka.javadsl.Consumer;
11 | import akka.kafka.javadsl.Producer;
12 | import akka.stream.Materializer;
13 | import akka.stream.javadsl.Sink;
14 | import akka.stream.javadsl.Source;
15 | import org.apache.kafka.clients.consumer.ConsumerConfig;
16 | import org.apache.kafka.clients.producer.ProducerConfig;
17 | import org.apache.kafka.clients.producer.ProducerRecord;
18 | import org.apache.kafka.common.serialization.ByteArrayDeserializer;
19 | import org.apache.kafka.common.serialization.ByteArraySerializer;
20 | import org.eclipse.microprofile.reactive.messaging.Message;
21 | import org.eclipse.microprofile.reactive.messaging.tck.container.SimpleMessage;
22 | import org.eclipse.microprofile.reactive.messaging.tck.container.TckMessagingPuppet;
23 | import org.jboss.arquillian.core.api.Instance;
24 | import org.jboss.arquillian.core.api.annotation.Inject;
25 |
26 | import java.time.Duration;
27 | import java.util.Optional;
28 | import java.util.concurrent.CompletionException;
29 | import java.util.concurrent.TimeUnit;
30 | import java.util.concurrent.TimeoutException;
31 | import java.util.concurrent.atomic.AtomicLong;
32 |
33 | public class KafkaTckMessagingPuppet implements TckMessagingPuppet {
34 |
35 | private static final AtomicLong ids = new AtomicLong();
36 |
37 | @Inject
38 | private Instance system;
39 | @Inject
40 | private Instance materializer;
41 |
42 | @Override
43 | public void sendMessage(String topic, Message message) {
44 | ProducerSettings settings = ProducerSettings.create(system.get(), new ByteArraySerializer(), new ByteArraySerializer())
45 | .withProperty(ProducerConfig.CLIENT_ID_CONFIG, "tck-local-container-controller-sender-" + ids.incrementAndGet())
46 | .withBootstrapServers("localhost:9092");
47 | try {
48 | Source.single(new ProducerRecord(topic, message.getPayload()))
49 | .runWith(Producer.plainSink(settings), materializer.get())
50 | .toCompletableFuture().get(testEnvironment().receiveTimeout().toMillis(), TimeUnit.MILLISECONDS);
51 | }
52 | catch (Exception e) {
53 | throw new RuntimeException(e);
54 | }
55 | }
56 |
57 | @Override
58 | public Optional> receiveMessage(String topic, Duration timeout) {
59 | ConsumerSettings settings = ConsumerSettings.create(system.get(), new ByteArrayDeserializer(), new ByteArrayDeserializer())
60 | .withProperty(ConsumerConfig.CLIENT_ID_CONFIG, "tck-local-container-controller-receiver-" + ids.incrementAndGet())
61 | .withGroupId("tck-local-container-receiver")
62 | .withProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")
63 | .withBootstrapServers("localhost:9092");
64 | try {
65 | Optional> message = Consumer.committableSource(settings, Subscriptions.topics(topic))
66 | .idleTimeout(timeout)
67 | .take(1)
68 | .mapAsync(1, msg ->
69 | msg.committableOffset().commitJavadsl()
70 | .thenApply(done ->
71 | Optional.of((Message) new SimpleMessage<>(msg.record().value()))
72 | )
73 | )
74 | .runWith(Sink.head(), materializer.get())
75 | .exceptionally(ex -> {
76 | Throwable unwrapped = ex;
77 | if (ex instanceof CompletionException) {
78 | unwrapped = ex.getCause();
79 | }
80 | if (unwrapped instanceof TimeoutException) {
81 | return Optional.empty();
82 | }
83 | else {
84 | throw new CompletionException(unwrapped);
85 | }
86 | }).toCompletableFuture().get(testEnvironment().receiveTimeout().toMillis(), TimeUnit.MILLISECONDS);
87 |
88 | return message;
89 | }
90 | catch (Exception e) {
91 | throw new RuntimeException(e);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/kafka-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2018 Lightbend Inc.
3 | #
4 | com.lightbend.microprofile.reactive.messaging.kafka.tck.client.KafkaArquillianTckExtension
5 | com.lightbend.microprofile.reactive.messaging.kafka.tck.container.KafkaArquillianTckRemoteExtension
6 | org.eclipse.microprofile.reactive.messaging.tck.container.TckArquillianRemoteExtension
--------------------------------------------------------------------------------
/kafka-tck/src/main/resources/application.conf:
--------------------------------------------------------------------------------
1 | akka {
2 | loggers = ["akka.event.slf4j.Slf4jLogger"]
3 | loglevel = "DEBUG"
4 | logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
5 | }
--------------------------------------------------------------------------------
/kafka-tck/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2018 Lightbend Inc.
3 | #
4 | # Set root logger level to DEBUG and its only appender to A1.
5 | log4j.rootLogger=WARN, CONSOLE
6 |
7 | # A1 is set to be a ConsoleAppender.
8 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
9 |
10 | # A1 uses PatternLayout.
11 | log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
12 | log4j.appender.CONSOLE.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
13 |
14 | log4j.logger.org.apache.kafka=WARN
--------------------------------------------------------------------------------
/kafka-tck/src/test/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaReactiveMessagingTckVerificationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.tck.ReactiveMessagingTck;
7 |
8 | public class KafkaReactiveMessagingTckVerificationTest extends ReactiveMessagingTck {
9 |
10 | }
--------------------------------------------------------------------------------
/kafka/out/production/resources/META-INF/services/javax.enterprise.inject.spi.Extension:
--------------------------------------------------------------------------------
1 | com.lightbend.microprofile.reactive.messaging.kafka.KafkaCdiExtension
--------------------------------------------------------------------------------
/kafka/out/test/resources/META-INF/services/org.eclipse.microprofile.reactive.messaging.tck.spi.TckContainer:
--------------------------------------------------------------------------------
1 | com.lightbend.microprofile.reactive.messaging.kafka.KafkaTckContainer
--------------------------------------------------------------------------------
/kafka/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 | 4.0.0
8 |
9 |
10 | com.lightbend.microprofile.reactive.messaging
11 | lightbend-microprofile-reactive-messaging-parent
12 | 1.0-SNAPSHOT
13 |
14 |
15 | lightbend-microprofile-reactive-messaging-kafka
16 | Lightbend MicroProfile Reactive Messaging Kafka
17 | Lightbend MicroProfile Reactive Messaging :: Kafka support
18 |
19 |
20 | ${project.groupId}
21 | lightbend-microprofile-reactive-messaging
22 | ${project.version}
23 |
24 |
25 | com.typesafe.akka
26 | akka-stream-kafka_2.12
27 |
28 |
29 |
30 |
31 |
32 |
33 | org.apache.maven.plugins
34 | maven-javadoc-plugin
35 |
36 |
37 | attach-javadocs
38 |
39 | jar
40 |
41 |
42 |
43 |
44 |
45 | org.apache.maven.plugins
46 | maven-source-plugin
47 |
48 |
49 | attach-sources
50 |
51 | jar
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/Kafka.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.MessagingProvider;
7 |
8 | public class Kafka implements MessagingProvider {
9 | private Kafka() {}
10 | }
11 |
--------------------------------------------------------------------------------
/kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaCdiExtension.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka;
5 |
6 | import javax.enterprise.event.Observes;
7 | import javax.enterprise.inject.spi.BeforeBeanDiscovery;
8 | import javax.enterprise.inject.spi.Extension;
9 |
10 | public class KafkaCdiExtension implements Extension {
11 |
12 | public void registerBeans(@Observes BeforeBeanDiscovery bbd) {
13 | bbd.addAnnotatedType(KafkaMessagingProvider.class, KafkaMessagingProvider.class.getName());
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaConsumerMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka;
5 |
6 | import org.eclipse.microprofile.reactive.messaging.Message;
7 |
8 | public interface KafkaConsumerMessage extends Message {
9 | long getOffset();
10 | K getKey();
11 | }
12 |
--------------------------------------------------------------------------------
/kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaConsumerMessageImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Lightbend Inc.
3 | */
4 | package com.lightbend.microprofile.reactive.messaging.kafka;
5 |
6 | import akka.kafka.ConsumerMessage;
7 | import org.eclipse.microprofile.reactive.messaging.Message;
8 |
9 | import java.util.concurrent.CompletionStage;
10 |
11 | class KafkaConsumerMessageImpl implements KafkaConsumerMessage {
12 | private final ConsumerMessage.CommittableMessage