├── .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 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 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 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 message; 13 | 14 | KafkaConsumerMessageImpl(ConsumerMessage.CommittableMessage message) { 15 | this.message = message; 16 | } 17 | 18 | @Override 19 | public K getKey() { 20 | return message.record().key(); 21 | } 22 | 23 | @Override 24 | public long getOffset() { 25 | return message.record().offset(); 26 | } 27 | 28 | @Override 29 | public T getPayload() { 30 | return message.record().value(); 31 | } 32 | 33 | @Override 34 | public CompletionStage ack() { 35 | return message.committableOffset().commitJavadsl().thenApply(d -> null); 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "KafkaConsumerMessage{" + 41 | "message=" + message + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaConsumerSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Lightbend Inc. 3 | */ 4 | package com.lightbend.microprofile.reactive.messaging.kafka; 5 | 6 | import com.lightbend.microprofile.reactive.messaging.spi.MessageDeserializer; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) 15 | public @interface KafkaConsumerSettings { 16 | String groupId() default ""; 17 | String bootstrapServers() default ""; 18 | Class keyDeserializer() default MessageDeserializer.class; 19 | } 20 | -------------------------------------------------------------------------------- /kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaInstanceProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Lightbend Inc. 3 | */ 4 | package com.lightbend.microprofile.reactive.messaging.kafka; 5 | 6 | import java.util.concurrent.CompletionStage; 7 | 8 | public interface KafkaInstanceProvider { 9 | CompletionStage bootstrapServers(); 10 | } 11 | -------------------------------------------------------------------------------- /kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaMessagingProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Lightbend Inc. 3 | */ 4 | package com.lightbend.microprofile.reactive.messaging.kafka; 5 | 6 | import akka.actor.ActorSystem; 7 | import akka.kafka.ConsumerSettings; 8 | import akka.kafka.ProducerSettings; 9 | import akka.stream.Materializer; 10 | import com.google.common.reflect.TypeToken; 11 | import com.lightbend.microprofile.reactive.messaging.spi.LightbendMessagingProvider; 12 | import com.lightbend.microprofile.reactive.messaging.spi.MessageDeserializer; 13 | import com.lightbend.microprofile.reactive.messaging.spi.MessageSerializer; 14 | import com.lightbend.microprofile.reactive.messaging.spi.PublishingStream; 15 | import com.lightbend.microprofile.reactive.messaging.spi.SubscribingStream; 16 | import com.lightbend.microprofile.reactive.messaging.spi.ValidatedPublishingStream; 17 | import com.lightbend.microprofile.reactive.messaging.spi.ValidatedSubscribingStream; 18 | import org.apache.kafka.clients.consumer.ConsumerConfig; 19 | import org.apache.kafka.common.serialization.ByteArrayDeserializer; 20 | import org.apache.kafka.common.serialization.ByteArraySerializer; 21 | import org.apache.kafka.common.serialization.Deserializer; 22 | import org.apache.kafka.common.serialization.Serializer; 23 | import org.apache.kafka.common.serialization.StringDeserializer; 24 | import org.apache.kafka.common.serialization.StringSerializer; 25 | import org.eclipse.microprofile.reactive.messaging.Message; 26 | import org.eclipse.microprofile.reactive.messaging.MessagingProvider; 27 | 28 | import javax.enterprise.context.ApplicationScoped; 29 | import javax.enterprise.inject.Instance; 30 | import javax.enterprise.inject.spi.Annotated; 31 | import javax.enterprise.inject.spi.AnnotatedMember; 32 | import javax.enterprise.inject.spi.AnnotatedMethod; 33 | import javax.enterprise.inject.spi.Bean; 34 | import javax.enterprise.inject.spi.BeanManager; 35 | import javax.enterprise.inject.spi.DeploymentException; 36 | import javax.inject.Inject; 37 | import java.lang.reflect.Member; 38 | import java.lang.reflect.ParameterizedType; 39 | import java.lang.reflect.Type; 40 | import java.util.Iterator; 41 | import java.util.Map; 42 | import java.util.Optional; 43 | 44 | @ApplicationScoped 45 | public class KafkaMessagingProvider implements LightbendMessagingProvider { 46 | 47 | private final ActorSystem system; 48 | private final Materializer materializer; 49 | private final Optional kafkaInstanceProvider; 50 | private final BeanManager beanManager; 51 | 52 | @Inject 53 | public KafkaMessagingProvider(ActorSystem system, Materializer materializer, Instance kafkaInstanceProviders, BeanManager beanManager) { 54 | this.system = system; 55 | this.materializer = materializer; 56 | this.beanManager = beanManager; 57 | 58 | 59 | Iterator iter = kafkaInstanceProviders.iterator(); 60 | if (iter.hasNext()) { 61 | this.kafkaInstanceProvider = Optional.of(iter.next()); 62 | } else { 63 | this.kafkaInstanceProvider = Optional.empty(); 64 | } 65 | } 66 | 67 | 68 | @Override 69 | public Class providerFor() { 70 | return Kafka.class; 71 | } 72 | 73 | @Override 74 | public ValidatedPublishingStream validatePublishingStream(PublishingStream stream) { 75 | if (stream.outgoing().value().equals("")) { 76 | throw new DeploymentException("Outgoing topic not defined on " + stream.annotated()); 77 | } 78 | return new KafkaValidatedPublishingStream<>(stream.outgoing().value(), createProducerSettings(stream), kafkaInstanceProvider, 79 | annotatedName(stream.annotated())); 80 | } 81 | 82 | private ProducerSettings createProducerSettings(PublishingStream stream) { 83 | 84 | KafkaProducerSettings producerSettingsAnnotation = stream.annotated().getAnnotation(KafkaProducerSettings.class); 85 | 86 | Serializer keySerializer; 87 | if (producerSettingsAnnotation != null && !producerSettingsAnnotation.keySerializer().equals(MessageSerializer.class)) { 88 | keySerializer = new KafkaMessageSerializer<>(getBean(producerSettingsAnnotation.keySerializer())); 89 | validateWrapperType(stream.wrapperType(), stream.annotated(), KafkaProducerMessage.class); 90 | } else { 91 | keySerializer = createKeySerializer(stream.wrapperType(), stream.annotated()); 92 | } 93 | 94 | Serializer serializer = new KafkaMessageSerializer<>(stream.createSerializer()); 95 | 96 | ProducerSettings producerSettings = ProducerSettings.create(system, keySerializer, serializer); 97 | 98 | if (producerSettingsAnnotation != null && !producerSettingsAnnotation.bootstrapServers().equals("")) { 99 | producerSettings = producerSettings.withBootstrapServers(producerSettingsAnnotation.bootstrapServers()); 100 | } 101 | 102 | return producerSettings; 103 | } 104 | 105 | @Override 106 | public ValidatedSubscribingStream validateSubscribingStream(SubscribingStream stream) { 107 | if (stream.incoming().value().equals("")) { 108 | throw new DeploymentException("Incoming topic not defined on " + stream.annotated()); 109 | } 110 | return new KafkaValidatedSubscribingStream<>(materializer, createConsumerSettings(stream), stream.incoming().value(), kafkaInstanceProvider, 111 | annotatedName(stream.annotated())); 112 | } 113 | 114 | private ConsumerSettings createConsumerSettings(SubscribingStream stream) { 115 | 116 | KafkaConsumerSettings consumerSettingsAnnotation = stream.annotated().getAnnotation(KafkaConsumerSettings.class); 117 | 118 | Deserializer keyDeserializer; 119 | if (consumerSettingsAnnotation != null && !consumerSettingsAnnotation.keyDeserializer().equals(MessageDeserializer.class)) { 120 | keyDeserializer = new KafkaMessageDeserializer<>(getBean(consumerSettingsAnnotation.keyDeserializer())); 121 | validateWrapperType(stream.wrapperType(), stream.annotated(), KafkaConsumerMessage.class); 122 | } else { 123 | keyDeserializer = createKeyDeserializer(stream.wrapperType(), stream.annotated()); 124 | } 125 | 126 | Deserializer deserializer = new KafkaMessageDeserializer<>(stream.createDeserializer()); 127 | 128 | String groupId; 129 | if (consumerSettingsAnnotation != null && !consumerSettingsAnnotation.groupId().equals("")) { 130 | groupId = consumerSettingsAnnotation.groupId(); 131 | } else { 132 | groupId = annotatedName(stream.annotated()); 133 | } 134 | 135 | ConsumerSettings consumerSettings = ConsumerSettings.create(system, 136 | keyDeserializer, deserializer) 137 | .withProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest") 138 | .withGroupId(groupId); 139 | 140 | if (consumerSettingsAnnotation != null && !consumerSettingsAnnotation.bootstrapServers().equals("")) { 141 | consumerSettings = consumerSettings.withBootstrapServers(consumerSettingsAnnotation.bootstrapServers()); 142 | } 143 | 144 | return consumerSettings; 145 | } 146 | 147 | private String annotatedName(Annotated annotated) { 148 | if (annotated instanceof AnnotatedMember) { 149 | Member member = ((AnnotatedMethod) annotated).getJavaMember(); 150 | return member.getDeclaringClass().getName() + "." + member.getName(); 151 | } else { 152 | return annotated.toString(); 153 | } 154 | } 155 | 156 | private Serializer createKeySerializer(Optional wrapperType, Annotated annotated) { 157 | if (wrapperType.isPresent()) { 158 | TypeToken type = TypeToken.of(wrapperType.get()); 159 | 160 | if (type.getRawType().equals(Message.class)) { 161 | return new ByteArraySerializer(); 162 | } else if (type.getRawType().equals(KafkaProducerMessage.class)) { 163 | // Find out the key type 164 | if (type.getType() instanceof ParameterizedType) { 165 | Type keyType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0]; 166 | if (keyType.equals(byte[].class)) { 167 | return new ByteArraySerializer(); 168 | } else if (keyType.equals(String.class)) { 169 | return new StringSerializer(); 170 | } else { 171 | throw new DeploymentException("Don't know how to serialize Kafka key type " + keyType + " for stream " + annotated); 172 | } 173 | } else { 174 | throw new DeploymentException(KafkaProducerMessage.class + " must be parameterized in stream " + annotated); 175 | } 176 | } else { 177 | throw new DeploymentException("Unsupported message wrapper type " + type.getType() + " for stream " + annotated); 178 | } 179 | } else { 180 | return new ByteArraySerializer(); 181 | } 182 | } 183 | 184 | private Deserializer createKeyDeserializer(Optional wrapperType, Annotated annotated) { 185 | if (wrapperType.isPresent()) { 186 | TypeToken type = TypeToken.of(wrapperType.get()); 187 | 188 | if (type.getRawType().equals(Message.class)) { 189 | return new ByteArrayDeserializer(); 190 | } else if (type.getRawType().equals(KafkaConsumerMessage.class)) { 191 | // Find out the key type 192 | if (type.getType() instanceof ParameterizedType) { 193 | Type keyType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0]; 194 | if (keyType.equals(byte[].class)) { 195 | return new ByteArrayDeserializer(); 196 | } else if (keyType.equals(String.class)) { 197 | return new StringDeserializer(); 198 | } else { 199 | throw new DeploymentException("Don't know how to deserialize Kafka key type " + keyType + " for stream " + annotated); 200 | } 201 | } else { 202 | throw new DeploymentException(KafkaConsumerMessage.class + " must be parameterized in stream " + annotated); 203 | } 204 | } else { 205 | throw new DeploymentException("Unsupported message wrapper type " + type.getType() + " for stream " + annotated); 206 | } 207 | } else { 208 | return new ByteArrayDeserializer(); 209 | } 210 | } 211 | 212 | private void validateWrapperType(Optional wrapperType, Annotated annotated, Class allowedWrapperType) { 213 | if (wrapperType.isPresent()) { 214 | TypeToken type = TypeToken.of(wrapperType.get()); 215 | 216 | if (!type.getRawType().equals(Message.class) && !type.getRawType().equals(allowedWrapperType)) { 217 | throw new DeploymentException("Unsupported message wrapper type " + type.getType() + " for stream " + annotated); 218 | } 219 | } 220 | } 221 | 222 | private T getBean(Class beanClass) { 223 | Iterator> beans = beanManager.getBeans(beanClass).iterator(); 224 | if (beans.hasNext()) { 225 | Bean bean = (Bean) beans.next(); 226 | return beanManager.getContext(bean.getScope()).get(bean); 227 | } else { 228 | throw new DeploymentException("No beans registered for class " + beanClass); 229 | } 230 | } 231 | 232 | private static class KafkaMessageDeserializer implements Deserializer { 233 | private final MessageDeserializer deserializer; 234 | 235 | private KafkaMessageDeserializer(MessageDeserializer deserializer) { 236 | this.deserializer = deserializer; 237 | } 238 | 239 | @Override 240 | public void configure(Map configs, boolean isKey) { 241 | } 242 | 243 | @Override 244 | public T deserialize(String topic, byte[] data) { 245 | return deserializer.fromBytes(data); 246 | } 247 | 248 | @Override 249 | public void close() { 250 | } 251 | } 252 | 253 | private static class KafkaMessageSerializer implements Serializer { 254 | private final MessageSerializer serializer; 255 | 256 | KafkaMessageSerializer(MessageSerializer serializer) { 257 | this.serializer = serializer; 258 | } 259 | 260 | @Override 261 | public void configure(Map configs, boolean isKey) { 262 | } 263 | 264 | @Override 265 | public byte[] serialize(String topic, T data) { 266 | return serializer.toBytes(data); 267 | } 268 | 269 | @Override 270 | public void close() { 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaProducerMessage.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 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.CompletionStage; 10 | import java.util.function.Supplier; 11 | 12 | public class KafkaProducerMessage implements Message { 13 | private final K key; 14 | private final T payload; 15 | private final Supplier> ack; 16 | 17 | public KafkaProducerMessage(K key, T payload) { 18 | this(key, payload, () -> CompletableFuture.completedFuture(null)); 19 | } 20 | 21 | public KafkaProducerMessage(T payload) { 22 | this(null, payload); 23 | } 24 | 25 | public KafkaProducerMessage(K key, T payload, Supplier> ack) { 26 | this.key = key; 27 | this.payload = payload; 28 | this.ack = ack; 29 | } 30 | 31 | public KafkaProducerMessage(T payload, Supplier> ack) { 32 | this(null, payload, ack); 33 | } 34 | 35 | public K getKey() { 36 | return key; 37 | } 38 | 39 | public T getPayload() { 40 | return payload; 41 | } 42 | 43 | @Override 44 | public CompletionStage ack() { 45 | return ack.get(); 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "KafkaProducerMessage{" + 51 | "key=" + key + 52 | ", payload=" + payload + 53 | ", ack=" + ack + 54 | '}'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaProducerSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Lightbend Inc. 3 | */ 4 | package com.lightbend.microprofile.reactive.messaging.kafka; 5 | 6 | import com.lightbend.microprofile.reactive.messaging.spi.MessageSerializer; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) 15 | public @interface KafkaProducerSettings { 16 | String bootstrapServers() default ""; 17 | Class keySerializer() default MessageSerializer.class; 18 | } 19 | -------------------------------------------------------------------------------- /kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaValidatedPublishingStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Lightbend Inc. 3 | */ 4 | package com.lightbend.microprofile.reactive.messaging.kafka; 5 | 6 | import akka.NotUsed; 7 | import akka.kafka.ProducerMessage; 8 | import akka.kafka.ProducerSettings; 9 | import akka.kafka.javadsl.Producer; 10 | import akka.stream.FlowShape; 11 | import akka.stream.Graph; 12 | import akka.stream.javadsl.Flow; 13 | import com.lightbend.microprofile.reactive.messaging.impl.FlowUtils; 14 | import com.lightbend.microprofile.reactive.messaging.spi.ValidatedPublishingStream; 15 | import org.apache.kafka.clients.producer.ProducerConfig; 16 | import org.apache.kafka.clients.producer.ProducerRecord; 17 | import org.eclipse.microprofile.reactive.messaging.Message; 18 | 19 | import java.util.Optional; 20 | import java.util.concurrent.CompletionStage; 21 | import java.util.concurrent.atomic.AtomicLong; 22 | 23 | class KafkaValidatedPublishingStream implements ValidatedPublishingStream { 24 | 25 | private final AtomicLong ids = new AtomicLong(); 26 | private final String topic; 27 | private final ProducerSettings producerSettings; 28 | private final Optional kafkaInstanceProvider; 29 | private final String clientId; 30 | 31 | KafkaValidatedPublishingStream(String topic, ProducerSettings producerSettings, Optional kafkaInstanceProvider, 32 | String clientId) { 33 | this.topic = topic; 34 | this.producerSettings = producerSettings; 35 | this.kafkaInstanceProvider = kafkaInstanceProvider; 36 | this.clientId = clientId; 37 | } 38 | 39 | @Override 40 | public Flow, Message, NotUsed> getFlowConsumer() { 41 | ProducerSettings castSettings = (ProducerSettings) producerSettings; 42 | 43 | if (producerSettings.properties().contains(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG)) { 44 | return getFlowConsumer(castSettings); 45 | } else if (kafkaInstanceProvider.isPresent()) { 46 | CompletionStage, Message>, NotUsed>> asyncFlow = 47 | kafkaInstanceProvider.get().bootstrapServers() 48 | .thenApply(bootstrapServers -> getFlowConsumer(castSettings.withBootstrapServers(bootstrapServers))); 49 | return FlowUtils.asynchronouslyProvidedFlow(asyncFlow); 50 | } else { 51 | // todo better default, probably read from config 52 | return getFlowConsumer(castSettings.withBootstrapServers("localhost:9092")); 53 | } 54 | } 55 | 56 | private Flow, Message, NotUsed> getFlowConsumer(ProducerSettings settings) { 57 | return Flow.>create() 58 | .map(message -> { 59 | if (message instanceof KafkaProducerMessage) { 60 | return new ProducerMessage.Message<>( 61 | new ProducerRecord<>(topic, ((KafkaProducerMessage) message).getKey(), message.getPayload()), 62 | message 63 | ); 64 | } 65 | else { 66 | return new ProducerMessage.Message<>( 67 | new ProducerRecord<>(topic, message.getPayload()), 68 | message 69 | ); 70 | } 71 | }).via(Producer.flow(settings.withProperty(ProducerConfig.CLIENT_ID_CONFIG, clientId + "-" + ids.incrementAndGet()))) 72 | .map(result -> result.message().passThrough()); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /kafka/src/main/java/com/lightbend/microprofile/reactive/messaging/kafka/KafkaValidatedSubscribingStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Lightbend Inc. 3 | */ 4 | package com.lightbend.microprofile.reactive.messaging.kafka; 5 | 6 | import akka.Done; 7 | import akka.NotUsed; 8 | import akka.japi.function.Creator; 9 | import akka.kafka.ConsumerSettings; 10 | import akka.kafka.Subscriptions; 11 | import akka.kafka.javadsl.Consumer; 12 | import akka.stream.Materializer; 13 | import akka.stream.javadsl.Flow; 14 | import akka.stream.javadsl.RestartSink; 15 | import akka.stream.javadsl.RestartSource; 16 | import akka.stream.javadsl.Sink; 17 | import akka.stream.javadsl.Source; 18 | import com.lightbend.microprofile.reactive.messaging.spi.ValidatedSubscribingStream; 19 | import org.apache.kafka.clients.consumer.ConsumerConfig; 20 | import org.eclipse.microprofile.reactive.messaging.Message; 21 | 22 | import java.io.Closeable; 23 | import java.time.Duration; 24 | import java.util.Optional; 25 | import java.util.concurrent.atomic.AtomicLong; 26 | import java.util.concurrent.atomic.AtomicReference; 27 | 28 | class KafkaValidatedSubscribingStream implements ValidatedSubscribingStream { 29 | 30 | private static final long MIN_BACKOFF_MS = 500; 31 | private static final long MAX_BACKOFF_MS = 30000; 32 | private static final double RANDOM_FACTOR = 0.2; 33 | private static final int MAX_RESTARTS = -1; 34 | private static final int ACK_PARALLELISM = 1; 35 | 36 | private final AtomicLong ids = new AtomicLong(); 37 | 38 | private final Materializer materializer; 39 | private final ConsumerSettings consumerSettings; 40 | private final String topic; 41 | private final Optional kafkaInstanceProvider; 42 | private final String clientId; 43 | 44 | public KafkaValidatedSubscribingStream(Materializer materializer, ConsumerSettings consumerSettings, String topic, 45 | Optional instanceProvider, String clientId) { 46 | this.materializer = materializer; 47 | this.consumerSettings = consumerSettings; 48 | this.topic = topic; 49 | this.kafkaInstanceProvider = instanceProvider; 50 | this.clientId = clientId; 51 | } 52 | 53 | @Override 54 | public Closeable runFlow(Creator, Message, NotUsed>> createConsumer) { 55 | ControlCloseable closeable = new ControlCloseable(); 56 | 57 | RestartSource.onFailuresWithBackoff( 58 | Duration.ofMillis(MIN_BACKOFF_MS), 59 | Duration.ofMillis(MAX_BACKOFF_MS), 60 | RANDOM_FACTOR, 61 | MAX_RESTARTS, 62 | () -> { 63 | Flow, Message, NotUsed> consumer = (Flow) createConsumer.create(); 64 | 65 | return getCommittableSource(closeable) 66 | .via(consumer) 67 | .mapAsync(ACK_PARALLELISM, message -> 68 | message.ack().thenApply(v -> Done.getInstance()) 69 | ); 70 | } 71 | ).runWith(Sink.ignore(), materializer); 72 | 73 | return closeable; 74 | } 75 | 76 | @Override 77 | public Closeable runSink(Creator, NotUsed>> createConsumer) { 78 | ControlCloseable closeable = new ControlCloseable(); 79 | 80 | Source, NotUsed> source = RestartSource.onFailuresWithBackoff( 81 | Duration.ofMillis(MIN_BACKOFF_MS), 82 | Duration.ofMillis(MAX_BACKOFF_MS), 83 | RANDOM_FACTOR, 84 | MAX_RESTARTS, 85 | () -> getCommittableSource(closeable) 86 | ); 87 | 88 | Sink, NotUsed> sink = RestartSink.withBackoff( 89 | Duration.ofMillis(MIN_BACKOFF_MS), 90 | Duration.ofMillis(MAX_BACKOFF_MS), 91 | RANDOM_FACTOR, 92 | MAX_RESTARTS, 93 | () -> (Sink) createConsumer.create() 94 | ); 95 | 96 | source.runWith(sink, materializer); 97 | 98 | return closeable; 99 | } 100 | 101 | private Source, NotUsed> getCommittableSource(ControlCloseable closeable) { 102 | if (consumerSettings.properties().contains(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG)) { 103 | return getCommittableSource(closeable, consumerSettings); 104 | } else if (kafkaInstanceProvider.isPresent()) { 105 | return Source.fromSourceCompletionStage( 106 | kafkaInstanceProvider.get().bootstrapServers() 107 | .thenApply(bootstrapServers -> 108 | getCommittableSource(closeable, consumerSettings.withBootstrapServers(bootstrapServers))) 109 | ).mapMaterializedValue(cs -> NotUsed.getInstance()); 110 | } else { 111 | // todo better default, probably read from config 112 | return getCommittableSource(closeable, consumerSettings.withBootstrapServers("localhost:9092")); 113 | } 114 | } 115 | 116 | private Source, NotUsed> getCommittableSource(ControlCloseable closeable, ConsumerSettings settings) { 117 | return Consumer.committableSource(settings.withProperty(ConsumerConfig.CLIENT_ID_CONFIG, clientId + "-" + ids.incrementAndGet()), 118 | Subscriptions.topics(topic)) 119 | .>map(KafkaConsumerMessageImpl::new) 120 | .mapMaterializedValue(control -> { 121 | closeable.set(control); 122 | return NotUsed.getInstance(); 123 | }); 124 | } 125 | 126 | private static class ControlCloseable implements Closeable { 127 | private AtomicReference closedOrControl = new AtomicReference<>(); 128 | 129 | @Override 130 | public void close() { 131 | Object old = closedOrControl.getAndSet(Closed.INSTANCE); 132 | if (old instanceof Consumer.Control) { 133 | ((Consumer.Control) old).shutdown(); 134 | } 135 | } 136 | 137 | void set(Consumer.Control control) { 138 | Object old; 139 | do { 140 | old = closedOrControl.get(); 141 | if (old == Closed.INSTANCE) { 142 | control.shutdown(); 143 | break; 144 | } 145 | } while (!closedOrControl.compareAndSet(old, control)); 146 | } 147 | 148 | private enum Closed { 149 | INSTANCE 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /kafka/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension: -------------------------------------------------------------------------------- 1 | com.lightbend.microprofile.reactive.messaging.kafka.KafkaCdiExtension -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 4.0.0 8 | 9 | com.lightbend.microprofile.reactive.messaging 10 | lightbend-microprofile-reactive-messaging-parent 11 | 1.0-SNAPSHOT 12 | pom 13 | Lightbend MicroProfile Reactive Messaging 14 | Lightbend MicroProfile Reactive Messaging :: Parent POM 15 | https://github.com/lightbend/lightbend-microprofile-messaging 16 | 17 | 18 | UTF-8 19 | 1.8 20 | 1.8 21 | 22 | 1.0-alpha-jroper-3 23 | 1.0 24 | 1.0.0 25 | 2.5.13 26 | 27 | 28 | 29 | 30 | Apache-2.0 31 | https://www.apache.org/licenses/LICENSE-2.0.txt 32 | repo 33 | A business-friendly OSS license 34 | 35 | 36 | 37 | 38 | Lightbend 39 | https://www.lightbend.com 40 | 41 | 42 | 43 | GitHub 44 | https://github.com/lightbend/microprofile-reactive-messaging/issues 45 | 46 | 47 | 48 | 49 | James Roper 50 | https://jazzy.id.au 51 | Lightbend 52 | https://www.lightbend.com 53 | 54 | 55 | 56 | 57 | scm:git:https://github.com/lightbend/microprofile-reactive-messaging.git 58 | scm:git:git@github.com:lightbend/microprofile-reactive-messaging.git 59 | https://github.com/lightbend/microprofile-reactive-messaging 60 | HEAD 61 | 62 | 63 | 64 | core 65 | akka 66 | kafka 67 | kafka-tck 68 | example 69 | 70 | 71 | 72 | 73 | 74 | com.lightbend.microprofile.reactive.streams 75 | lightbend-microprofile-reactive-streams-akka 76 | ${microprofile.reactive.streams.akka.version} 77 | 78 | 79 | org.eclipse.microprofile.reactive-streams-operators 80 | microprofile-reactive-streams-operators-api 81 | ${microprofile.reactive-streams-operators.version} 82 | 83 | 84 | org.eclipse.microprofile.reactive-streams-operators 85 | microprofile-reactive-streams-operators-core 86 | ${microprofile.reactive-streams-operators.version} 87 | 88 | 89 | org.eclipse.microprofile.reactive.messaging 90 | microprofile-reactive-messaging-api 91 | ${microprofile.reactive.messaging.version} 92 | 93 | 94 | org.eclipse.microprofile.reactive.messaging 95 | microprofile-reactive-messaging-tck 96 | ${microprofile.reactive.messaging.version} 97 | 98 | 99 | javax.enterprise 100 | cdi-api 101 | 2.0 102 | 103 | 104 | com.google.guava 105 | guava 106 | 25.0-jre 107 | 108 | 109 | javax.json.bind 110 | javax.json.bind-api 111 | 1.0 112 | 113 | 114 | org.eclipse 115 | yasson 116 | 1.0.1 117 | 118 | 119 | com.typesafe.akka 120 | akka-actor_2.12 121 | ${akka.version} 122 | 123 | 124 | com.typesafe.akka 125 | akka-stream_2.12 126 | ${akka.version} 127 | 128 | 129 | com.typesafe.akka 130 | akka-slf4j_2.12 131 | ${akka.version} 132 | 133 | 134 | com.typesafe.akka 135 | akka-stream-kafka_2.12 136 | 0.20 137 | 138 | 139 | org.jboss.weld.se 140 | weld-se-core 141 | 3.0.4.Final 142 | 143 | 144 | org.jboss.arquillian 145 | arquillian-bom 146 | 1.4.0.Final 147 | import 148 | pom 149 | 150 | 151 | org.jboss.arquillian.container 152 | arquillian-weld-embedded 153 | 2.0.0.Final 154 | 155 | 156 | org.testng 157 | testng 158 | 6.14.3 159 | 160 | 161 | org.glassfish 162 | javax.json 163 | 1.1.2 164 | 165 | 166 | org.slf4j 167 | slf4j-log4j12 168 | 1.7.12 169 | 170 | 171 | org.apache.kafka 172 | kafka-clients 173 | 1.1.0 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | org.apache.maven.plugins 183 | maven-jar-plugin 184 | 3.0.2 185 | 186 | 187 | org.apache.maven.plugins 188 | maven-source-plugin 189 | 3.0.1 190 | 191 | 192 | org.apache.maven.plugins 193 | maven-javadoc-plugin 194 | 2.10.4 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 206 | custom-deploy 207 | 208 | 209 | ${custom-deploy.id} 210 | ${custom-deploy.url} 211 | 212 | 213 | ${custom-deploy.id} 214 | ${custom-deploy.url} 215 | 216 | 217 | 218 | 219 | 220 | --------------------------------------------------------------------------------