├── .gitignore
├── LICENSE
├── README.md
├── component
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── io
│ │ └── siddhi
│ │ └── extension
│ │ └── io
│ │ └── kafka
│ │ ├── Constants.java
│ │ ├── KafkaIOUtils.java
│ │ ├── metrics
│ │ ├── Metrics.java
│ │ ├── SinkMetrics.java
│ │ └── SourceMetrics.java
│ │ ├── multidc
│ │ ├── sink
│ │ │ └── KafkaMultiDCSink.java
│ │ └── source
│ │ │ ├── KafkaMultiDCSource.java
│ │ │ └── SourceSynchronizer.java
│ │ ├── sink
│ │ ├── KafkaReplayRequestSink.java
│ │ └── KafkaSink.java
│ │ ├── source
│ │ ├── ConsumerKafkaGroup.java
│ │ ├── KafkaConsumerThread.java
│ │ ├── KafkaReplayResponseSource.java
│ │ ├── KafkaReplayThread.java
│ │ └── KafkaSource.java
│ │ └── util
│ │ └── KafkaReplayResponseSourceRegistry.java
│ └── test
│ ├── java
│ └── io
│ │ └── siddhi
│ │ └── extension
│ │ └── io
│ │ └── kafka
│ │ ├── KafkaTestUtil.java
│ │ ├── SequencedMessagingTestCase.java
│ │ ├── UnitTestAppender.java
│ │ ├── multidc
│ │ ├── KafkaMultiDCSinkTestCases.java
│ │ ├── KafkaMultiDCSourceSynchronizerTestCases.java
│ │ └── KafkaMultiDCSourceTestCases.java
│ │ ├── sink
│ │ ├── ErrorHandlingTestCase.java
│ │ ├── KafkaSinkTestCase.java
│ │ └── KafkaSinkwithBinaryMapperTestCase.java
│ │ └── source
│ │ ├── KafkaSourceHATestCase.java
│ │ └── KafkaSourceTestCase.java
│ └── resources
│ ├── log4j.properties
│ ├── log4j2.xml
│ └── testng.xml
├── docs
├── api
│ ├── 4.0.10.md
│ ├── 4.0.11.md
│ ├── 4.0.12.md
│ ├── 4.0.13.md
│ ├── 4.0.14.md
│ ├── 4.0.15.md
│ ├── 4.0.16.md
│ ├── 4.0.17.md
│ ├── 4.0.7.md
│ ├── 4.0.8.md
│ ├── 4.0.9.md
│ ├── 4.1.0.md
│ ├── 4.1.1.md
│ ├── 4.1.10.md
│ ├── 4.1.11.md
│ ├── 4.1.12.md
│ ├── 4.1.13.md
│ ├── 4.1.14.md
│ ├── 4.1.15.md
│ ├── 4.1.16.md
│ ├── 4.1.17.md
│ ├── 4.1.18.md
│ ├── 4.1.19.md
│ ├── 4.1.2.md
│ ├── 4.1.20.md
│ ├── 4.1.21.md
│ ├── 4.1.3.md
│ ├── 4.1.4.md
│ ├── 4.1.5.md
│ ├── 4.1.6.md
│ ├── 4.1.7.md
│ ├── 4.1.8.md
│ ├── 4.1.9.md
│ ├── 4.2.0.md
│ ├── 4.2.1.md
│ ├── 5.0.0.md
│ ├── 5.0.1.md
│ ├── 5.0.10.md
│ ├── 5.0.11.md
│ ├── 5.0.12.md
│ ├── 5.0.13.md
│ ├── 5.0.14.md
│ ├── 5.0.15.md
│ ├── 5.0.16.md
│ ├── 5.0.17.md
│ ├── 5.0.18.md
│ ├── 5.0.19.md
│ ├── 5.0.2.md
│ ├── 5.0.3.md
│ ├── 5.0.4.md
│ ├── 5.0.5.md
│ ├── 5.0.6.md
│ ├── 5.0.7.md
│ ├── 5.0.8.md
│ ├── 5.0.9.md
│ └── latest.md
├── assets
│ ├── javascripts
│ │ └── extra.js
│ ├── lib
│ │ ├── backtotop
│ │ │ ├── img
│ │ │ │ └── cd-top-arrow.svg
│ │ │ └── js
│ │ │ │ ├── main.js
│ │ │ │ └── util.js
│ │ └── highlightjs
│ │ │ ├── default.min.css
│ │ │ └── highlight.min.js
│ └── stylesheets
│ │ └── extra.css
├── images
│ ├── favicon.ico
│ └── siddhi-logo.svg
├── index.md
└── license.md
├── findbugs-exclude.xml
├── issue_template.md
├── mkdocs.yml
├── pom.xml
└── pull_request_template.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.ear
17 | *.zip
18 | *.tar.gz
19 | *.rar
20 |
21 | tmp_kafka*
22 | .idea
23 | target
24 | *.iml
25 | site
26 | .DS_Store
27 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
28 | hs_err_pid*
--------------------------------------------------------------------------------
/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 | Siddhi IO Kafka
2 | ======================================
3 |
4 | [](https://wso2.org/jenkins/job/siddhi/job/siddhi-io-kafka/)
5 | [](https://github.com/siddhi-io/siddhi-io-kafka/releases)
6 | [](https://github.com/siddhi-io/siddhi-io-kafka/releases)
7 | [](https://github.com/siddhi-io/siddhi-io-kafka/issues)
8 | [](https://github.com/siddhi-io/siddhi-io-kafka/commits/master)
9 | [](https://opensource.org/licenses/Apache-2.0)
10 |
11 | The **siddhi-io-kafka extension** is an extension to Siddhi that receives and publishes events from and to Kafka.
12 |
13 | For information on Siddhi and it's features refer Siddhi Documentation.
14 |
15 | ## Download
16 |
17 | * Versions 5.x and above with group id `io.siddhi.extension.*` from here.
18 | * Versions 4.x and lower with group id `org.wso2.extension.siddhi.*` from here.
19 |
20 | ## Latest API Docs
21 |
22 | Latest API Docs is 5.0.19.
23 |
24 | ## Features
25 |
26 | * kafka *(Sink)*
A Kafka sink publishes events processed by WSO2 SP to a topic with a partition for a Kafka cluster. The events can be published in the TEXTXMLJSON or Binary format. If the topic is not already created in the Kafka cluster, the Kafka sink creates the default partition for the given topic. The publishing topic and partition can be a dynamic value taken from the Siddhi event. To configure a sink to use the Kafka transport, the type parameter should have kafka as its value.
A Kafka sink publishes events processed by WSO2 SP to a topic with a partition for a Kafka cluster. The events can be published in the TEXTXMLJSON or Binary format. If the topic is not already created in the Kafka cluster, the Kafka sink creates the default partition for the given topic. The publishing topic and partition can be a dynamic value taken from the Siddhi event. To configure a sink to publish events via the Kafka transport, and using two Kafka brokers to publish events to the same topic, the type parameter must have kafkaMultiDC as its value.
A Kafka source receives events to be processed by WSO2 SP from a topic with a partition for a Kafka cluster. The events received can be in the TEXTXMLJSON or Binary format. If the topic is not already created in the Kafka cluster, the Kafka sink creates the default partition for the given topic.
The Kafka Multi-Datacenter(DC) source receives records from the same topic in brokers deployed in two different kafka clusters. It filters out all the duplicate messages and ensuresthat the events are received in the correct order using sequential numbering. It receives events in formats such as TEXT, XML JSON and Binary`.The Kafka Source creates the default partition '0' for a given topic, if the topic has not yet been created in the Kafka cluster.
32 |
33 | ## Installation
34 |
35 | For installing this extension in the Streaming Integrator Server, and to add the dependent jars, refer Streaming Integrator documentation section on downloading and installing siddhi extensions.\
36 | For installing this extension in the Streaming Integrator Tooling, and to add the dependent jars, refer Streaming Integrator documentation section on installing siddhi extensions.
37 |
38 | ## Dependencies
39 |
40 | Following JARs will be converted to osgi and copied to `WSO2SI_HOME/lib` and `WSO2SI_HOME/samples/sample-clients/lib` which are in `/libs` directory.
41 |
42 | - kafka_2.11-*.jar
43 | - kafka-clients-*.jar
44 | - metrics-core-*.jar
45 | - scala-library-2.11.*.jar
46 | - scala-parser-combinators_2.11.*.jar (if exists)
47 | - zkclient-*.jar
48 | - zookeeper-*.jar
49 |
50 | #### Setup Kafka
51 |
52 | As a prerequisite, you have to start the Kafka message broker. Please follow better steps.
53 | 1. Download the Kafka [distribution](https://kafka.apache.org/downloads)
54 | 2. Unzip the above distribution and go to the ‘bin’ directory
55 | 3. Start the zookeeper by executing below command,
56 | ```bash
57 | zookeeper-server-start.sh config/zookeeper.properties
58 | ```
59 | 4. Start the Kafka broker by executing below command,
60 | ```bash
61 | kafka-server-start.sh config/server.properties
62 | ```
63 |
64 | Refer the Kafka documentation for more details, https://kafka.apache.org/quickstart
65 |
66 | ## Support and Contribution
67 |
68 | * We encourage users to ask questions and get support via StackOverflow, make sure to add the `siddhi` tag to the issue for better response.
69 |
70 | * If you find any issues related to the extension please report them on the issue tracker.
71 |
72 | * For production support and other contribution related information refer Siddhi Community documentation.
73 |
--------------------------------------------------------------------------------
/component/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
22 |
23 | io.siddhi.extension.io.kafka
24 | siddhi-io-kafka-parent
25 | 5.0.20-SNAPSHOT
26 | ../pom.xml
27 |
28 | 4.0.0
29 | bundle
30 |
31 | siddhi-io-kafka
32 | Siddhi Extension - Kafka Transport
33 |
34 |
35 |
36 | io.siddhi
37 | siddhi-query-api
38 |
39 |
40 | io.siddhi
41 | siddhi-annotations
42 |
43 |
44 | io.siddhi
45 | siddhi-core
46 |
47 |
48 | org.apache.logging.log4j
49 | log4j-core
50 |
51 |
52 | org.testng
53 | testng
54 | test
55 |
56 |
57 | org.apache.kafka
58 | kafka_2.11
59 |
60 |
61 |
62 |
63 | org.apache.curator
64 | curator-test
65 |
66 |
67 | org.apache.zookeeper
68 | zookeeper
69 |
70 |
71 | commons-io
72 | commons-io
73 | test
74 |
75 |
76 | io.siddhi.extension.map.xml
77 | siddhi-map-xml
78 |
79 |
80 | io.siddhi.extension.map.binary
81 | siddhi-map-binary
82 |
83 |
84 | io.confluent
85 | common-config
86 |
87 |
88 | io.confluent
89 | common-utils
90 |
91 |
92 | io.confluent
93 | kafka-schema-registry-client
94 |
95 |
96 | io.confluent
97 | kafka-avro-serializer
98 |
99 |
100 | org.jacoco
101 | org.jacoco.agent
102 | runtime
103 | test
104 |
105 |
106 | io.siddhi.extension.map.text
107 | siddhi-map-text
108 | test
109 |
110 |
111 | org.wso2.carbon.analytics
112 | org.wso2.carbon.si.metrics.core
113 |
114 |
115 | com.google.protobuf
116 | protobuf-java
117 |
118 |
119 | org.codehaus.jackson
120 | jackson-jaxrs
121 |
122 |
123 | com.fasterxml.jackson.core
124 | jackson-databind
125 |
126 |
127 | com.fasterxml.jackson.core
128 | jackson-core
129 |
130 |
131 | com.fasterxml.jackson.core
132 | jackson-annotations
133 |
134 |
135 | log4j
136 | log4j
137 | test
138 |
139 |
140 |
141 |
142 |
143 | documentation-deploy
144 |
145 |
146 |
147 | io.siddhi
148 | siddhi-doc-gen
149 | ${siddhi.version}
150 |
151 |
152 | compile
153 |
154 | deploy-mkdocs-github-pages
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | org.apache.felix
168 | maven-bundle-plugin
169 | true
170 |
171 |
172 | ${project.artifactId}
173 | ${project.artifactId}
174 |
175 | io.siddhi.extension.io.kafka.*,
176 | io.confluent.*,
177 | !com.google.protobuf.*,
178 | org.codehaus.jackson.*,
179 |
180 |
181 | io.siddhi.core.*;version="${siddhi.version.range}",
182 | io.siddhi.annotation.*;version="${siddhi.version.range}",
183 | io.siddhi.query.api.*;version="${siddhi.version.range}",
184 | *;resolution:=optional
185 |
186 |
187 | com.google.*,
188 |
189 |
190 | META-INF=target/classes/META-INF
191 |
192 |
193 |
194 |
195 |
196 | org.apache.maven.plugins
197 | maven-compiler-plugin
198 |
199 | 1.8
200 | 1.8
201 |
202 |
203 |
204 | org.apache.maven.plugins
205 | maven-surefire-plugin
206 |
207 |
208 | src/test/resources/testng.xml
209 |
210 |
211 |
212 |
213 | org.jacoco
214 | jacoco-maven-plugin
215 |
216 |
217 | io.siddhi
218 | siddhi-doc-gen
219 | ${siddhi.version}
220 |
221 |
222 | compile
223 |
224 | generate-md-docs
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/Constants.java:
--------------------------------------------------------------------------------
1 | package io.siddhi.extension.io.kafka;
2 |
3 | /**
4 | * Constants used in kafka executions.
5 | * */
6 | public class Constants {
7 | public static final String TRP_RECORD_TIMESTAMP = "record.timestamp";
8 | public static final String TRP_EVENT_TIMESTAMP = "event.timestamp";
9 | public static final String TRP_CHECK_SUM = "check.sum";
10 | public static final String TRP_TOPIC = "topic";
11 | public static final String TRP_PARTITION = "partition";
12 | public static final String TRP_KEY = "key";
13 | public static final String TRP_OFFSET = "offset";
14 | public static final String SINK_ID = "sink.id";
15 | /*prometheus reporte values*/
16 | public static final String PROMETHEUS_REPORTER_NAME = "prometheus";
17 | }
18 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/KafkaIOUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka;
20 |
21 | import org.apache.logging.log4j.LogManager;
22 | import org.apache.logging.log4j.Logger;
23 |
24 | import java.util.Properties;
25 |
26 | /**
27 | * Util class of Kafka IO.
28 | */
29 | public class KafkaIOUtils {
30 |
31 | public static final String HEADER_SEPARATOR = ",";
32 | private static final String ENTRY_SEPARATOR = ":";
33 | private static final Logger LOG = LogManager.getLogger(KafkaIOUtils.class);
34 |
35 | public static void splitHeaderValues(String optionalConfigs, Properties configProperties) {
36 | if (optionalConfigs != null && !optionalConfigs.isEmpty()) {
37 | String[] optionalProperties = optionalConfigs.split(HEADER_SEPARATOR);
38 | if (optionalProperties.length > 0) {
39 | for (String header : optionalProperties) {
40 | try {
41 | String[] configPropertyWithValue = header.split(ENTRY_SEPARATOR, 2);
42 | configProperties.put(configPropertyWithValue[0], configPropertyWithValue[1]);
43 | } catch (Exception e) {
44 | LOG.warn("Optional property '{}' is not defined in the correct format.", header, e);
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/metrics/Metrics.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka.metrics;
20 |
21 | /**
22 | * Parent metric class for Kafka Source and Sink
23 | */
24 | public class Metrics {
25 |
26 | protected String siddhiAppName;
27 | protected String streamId;
28 |
29 | protected Metrics (String siddhiAppName, String streamId) {
30 | this.siddhiAppName = siddhiAppName;
31 | this.streamId = streamId;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/metrics/SinkMetrics.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka.metrics;
20 |
21 | import org.wso2.carbon.metrics.core.Counter;
22 | import org.wso2.carbon.metrics.core.Level;
23 | import org.wso2.carbon.si.metrics.core.internal.MetricsDataHolder;
24 |
25 | import java.util.Map;
26 | import java.util.concurrent.ConcurrentHashMap;
27 |
28 | /**
29 | * Metric class for Kafka Sink
30 | */
31 | public class SinkMetrics extends Metrics {
32 | private Map> offsetMap = new ConcurrentHashMap<>();
33 | private Map> latencyMap = new ConcurrentHashMap<>();
34 | private Map> messageSizeMap = new ConcurrentHashMap<>();
35 | private Map> lastMessagePublishedTimeMap = new ConcurrentHashMap<>();
36 |
37 | public SinkMetrics(String siddhiAppName, String streamId) {
38 | super(siddhiAppName, streamId);
39 | }
40 |
41 | public Counter getTotalWrites() {
42 | return MetricsDataHolder.getInstance().getMetricService()
43 | .counter(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Total.Writes.%s", siddhiAppName, "kafka"),
44 | Level.INFO);
45 | }
46 |
47 | public Counter getWriteCountPerStream(String streamId, String topic, int partition) {
48 | return MetricsDataHolder.getInstance().getMetricService()
49 | .counter(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Sink.Writes.Per.Stream.%s.%s.%s",
50 | siddhiAppName, topic, "stream_id." + streamId, "partition." + partition)
51 | , Level.INFO);
52 | }
53 |
54 | public Counter getErrorCountWithoutPartition(String topic, String streamId, String errorString) {
55 | return MetricsDataHolder.getInstance().getMetricService()
56 | .counter(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Sink.Errors.Without.Partition.%s.%s.%s",
57 | siddhiAppName, topic, "stream_id." + streamId, "errorString." + errorString), Level.INFO);
58 | }
59 |
60 | public Counter getErrorCountPerStream(String streamId, String topic, int partition, String errorString) {
61 | return MetricsDataHolder.getInstance().getMetricService()
62 | .counter(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Sink.Errors.Per.Stream.%s.%s.%s.%s",
63 | siddhiAppName, topic, "stream_id." + streamId, "partition." + partition, "errorString." +
64 | errorString), Level.INFO);
65 | }
66 |
67 | public void getLastMessageSize(String topic, int partition, String streamId, double messageSize) {
68 | updateMessageSizeMap(topic, partition, messageSize);
69 | MetricsDataHolder.getInstance().getMetricService()
70 | .gauge(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Sink.Per.Stream.%s.%s.%s.%s",
71 | siddhiAppName, topic, "partition." + partition,
72 | "streamId." + streamId, "last_message_size_in_bytes"),
73 | Level.INFO, () -> messageSizeMap.get(topic).get(partition));
74 | }
75 |
76 | public void getLastMessageAckLatency(String topic, int partition, String streamId, long latency) {
77 | updateLatencyMap(topic, partition, latency);
78 | MetricsDataHolder.getInstance().getMetricService()
79 | .gauge(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Sink.Per.Stream.%s.%s.%s.%s",
80 | siddhiAppName, topic, "partition." + partition,
81 | "streamId." + streamId, "last_message_latency_in_millis"),
82 | Level.INFO, () -> latencyMap.get(topic).get(partition));
83 | }
84 |
85 | public void getLastCommittedOffset(String topic, int partition, String streamId, long offset) {
86 | updateOffsetMap(topic, partition, offset);
87 | MetricsDataHolder.getInstance().getMetricService()
88 | .gauge(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Sink.Current.Offset.%s.%s.%s",
89 | siddhiAppName, topic, "partition." + partition, "streamId." + streamId), Level.INFO,
90 | () -> offsetMap.get(topic).get(partition));
91 | }
92 |
93 | public void getLastMessagePublishedTime(String topic, int partition, String streamId, long pushedTimestamp) {
94 | updateLastMessagePublishedTimeMap(topic, partition, pushedTimestamp);
95 | MetricsDataHolder.getInstance().getMetricService()
96 | .gauge(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Sink.Per.Stream.%s.%s.%s.%s",
97 | siddhiAppName, topic, "partition." + partition,
98 | "streamId." + streamId, "last_message_published_at"),
99 | Level.INFO, System::currentTimeMillis);
100 | }
101 |
102 | private void updateOffsetMap(String topic, int partition, long offset) {
103 | Map partitionMap;
104 | if (offsetMap.get(topic) == null) {
105 | partitionMap = new ConcurrentHashMap();
106 | } else {
107 | partitionMap = offsetMap.get(topic);
108 | }
109 | partitionMap.put(partition, offset);
110 | offsetMap.put(topic, partitionMap);
111 | }
112 |
113 | private void updateLatencyMap(String topic, int partition, long latency) {
114 | Map partitionMap;
115 | if (latencyMap.get(topic) == null) {
116 | partitionMap = new ConcurrentHashMap();
117 | } else {
118 | partitionMap = latencyMap.get(topic);
119 | }
120 | partitionMap.put(partition, latency);
121 | latencyMap.put(topic, partitionMap);
122 | }
123 |
124 | private void updateMessageSizeMap(String topic, int partition, double messageSize) {
125 | Map partitionMap;
126 | if (messageSizeMap.get(topic) == null) {
127 | partitionMap = new ConcurrentHashMap();
128 | } else {
129 | partitionMap = messageSizeMap.get(topic);
130 | }
131 | partitionMap.put(partition, messageSize);
132 | messageSizeMap.put(topic, partitionMap);
133 | }
134 |
135 | private void updateLastMessagePublishedTimeMap(String topic, int partition, long pushedTimestamp) {
136 | Map partitionMap;
137 | if (lastMessagePublishedTimeMap.get(topic) == null) {
138 | partitionMap = new ConcurrentHashMap();
139 | } else {
140 | partitionMap = lastMessagePublishedTimeMap.get(topic);
141 | }
142 | partitionMap.put(partition, pushedTimestamp);
143 | lastMessagePublishedTimeMap.put(topic, partitionMap);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/metrics/SourceMetrics.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka.metrics;
20 |
21 | import org.wso2.carbon.metrics.core.Counter;
22 | import org.wso2.carbon.metrics.core.Level;
23 | import org.wso2.carbon.si.metrics.core.internal.MetricsDataHolder;
24 |
25 | import java.util.Map;
26 |
27 | /**
28 | * Metric class for Kafka Source
29 | */
30 | public class SourceMetrics extends Metrics {
31 | private Map> topicOffsetMap = null;
32 | private long consumerLag;
33 |
34 | public SourceMetrics(String siddhiAppName, String streamId) {
35 | super(siddhiAppName, streamId);
36 | }
37 |
38 | public Counter getTotalReads() { //to count the total reads from siddhi app level.
39 | return MetricsDataHolder.getInstance().getMetricService()
40 | .counter(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Total.Reads.%s", siddhiAppName, "kafka"),
41 | Level.INFO);
42 | }
43 |
44 | public Counter getReadCountPerStream(String topic, Integer partition, String groupId) {
45 | return MetricsDataHolder.getInstance().getMetricService()
46 | .counter(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Source.Reads.Per.Stream.%s.%s.%s.%s",
47 | siddhiAppName, topic, "stream_id." + streamId, "partition." + partition, "groupId." + groupId)
48 | , Level.INFO);
49 | }
50 |
51 | public void getCurrentOffset(String topic, Integer partition, String groupId) {
52 | if (topicOffsetMap != null) {
53 | MetricsDataHolder.getInstance().getMetricService()
54 | .gauge(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Source.Current.Offset.%s.%s.%s.%s",
55 | siddhiAppName, topic, "partition." + partition, "groupId." + groupId,
56 | "stream_id." + streamId), Level.INFO, () -> topicOffsetMap.get(topic).get(partition));
57 | }
58 | }
59 |
60 | public Counter getErrorCountPerStream(String topic, String groupId, String errorString) {
61 | return MetricsDataHolder.getInstance().getMetricService()
62 | .counter(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Source.Errors.Per.Stream.%s.%s.%s.%s",
63 | siddhiAppName, topic, "stream_id." + streamId, "groupId." + groupId, "errorString." +
64 | errorString), Level.INFO);
65 | }
66 |
67 | public void getLastMessageConsumedTime(String topic, String groupId) {
68 | MetricsDataHolder.getInstance().getMetricService()
69 | .gauge(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Source.Per.Stream.%s.%s.%s.%s",
70 | siddhiAppName, topic, "groupId." + groupId,
71 | "streamId." + streamId, "last_message_consumed_at"),
72 | Level.INFO, System::currentTimeMillis);
73 | }
74 |
75 | public synchronized void getConsumerLag(String topic, String groupId, int partition, long recordTimestamp) {
76 | setConsumerLag(System.currentTimeMillis() - recordTimestamp);
77 | MetricsDataHolder.getInstance().getMetricService()
78 | .gauge(String.format("io.siddhi.SiddhiApps.%s.Siddhi.Kafka.Source.Per.Stream.%s.%s.%s.%s.%s",
79 | siddhiAppName, topic, "partition." + partition, "groupId." + groupId,
80 | "streamId." + streamId, "consumer_lag"),
81 | Level.INFO, () -> getConsumerLag());
82 | }
83 |
84 | public void setTopicOffsetMap(Map> topicOffsetMap) {
85 | this.topicOffsetMap = topicOffsetMap;
86 | }
87 |
88 | public long getConsumerLag() {
89 | return consumerLag;
90 | }
91 |
92 | public void setConsumerLag(long consumerLag) {
93 | this.consumerLag = consumerLag;
94 | }
95 | }
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/multidc/sink/KafkaMultiDCSink.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka.multidc.sink;
20 |
21 | import io.siddhi.annotation.Example;
22 | import io.siddhi.annotation.Extension;
23 | import io.siddhi.annotation.Parameter;
24 | import io.siddhi.annotation.util.DataType;
25 | import io.siddhi.core.config.SiddhiAppContext;
26 | import io.siddhi.core.exception.ConnectionUnavailableException;
27 | import io.siddhi.core.util.config.ConfigReader;
28 | import io.siddhi.core.util.snapshot.state.StateFactory;
29 | import io.siddhi.core.util.transport.DynamicOptions;
30 | import io.siddhi.core.util.transport.OptionHolder;
31 | import io.siddhi.extension.io.kafka.KafkaIOUtils;
32 | import io.siddhi.extension.io.kafka.sink.KafkaSink;
33 | import io.siddhi.query.api.definition.StreamDefinition;
34 | import io.siddhi.query.api.exception.SiddhiAppValidationException;
35 | import org.apache.kafka.clients.producer.KafkaProducer;
36 | import org.apache.kafka.clients.producer.Producer;
37 | import org.apache.kafka.clients.producer.ProducerRecord;
38 | import org.apache.logging.log4j.LogManager;
39 | import org.apache.logging.log4j.Logger;
40 |
41 | import java.io.UnsupportedEncodingException;
42 | import java.nio.ByteBuffer;
43 | import java.util.ArrayList;
44 | import java.util.List;
45 | import java.util.Properties;
46 |
47 | /**
48 | * This class implements a Kafka sink to publish Siddhi events to multiple kafka clusters. This sink is useful in
49 | * multi data center deployments where we have two identical setups replicated in two physical locations
50 | */
51 | @Extension(
52 | name = "kafkaMultiDC",
53 | namespace = "sink",
54 | description = "A Kafka sink publishes events processed by WSO2 SP to a topic with a partition for a Kafka " +
55 | "cluster. The events can be published in the `TEXT` `XML` `JSON` or `Binary` format.\n" +
56 | "If the topic is not already created in the Kafka cluster, the Kafka sink creates the default " +
57 | "partition for the given topic. The publishing topic and partition can be a dynamic value taken " +
58 | "from the Siddhi event.\n" +
59 | "To configure a sink to publish events via the Kafka transport, and using two Kafka brokers to " +
60 | "publish events to the same topic, the `type` parameter must have `kafkaMultiDC` as its value.",
61 | parameters = {
62 | @Parameter(name = "bootstrap.servers",
63 | description = " This parameter specifies the list of Kafka servers to which the Kafka " +
64 | "sink must publish events. This list should be provided as a set of comma " +
65 | "-separated values. There must be " +
66 | "at least two servers in this list. e.g., `localhost:9092,localhost:9093`.",
67 | type = {DataType.STRING}),
68 | @Parameter(name = "topic",
69 | description = "The topic to which the Kafka sink needs to publish events. Only one " +
70 | "topic must be specified.",
71 | type = {DataType.STRING}),
72 | @Parameter(name = "sequence.id",
73 | description = "A unique identifier to identify the messages published by this sink. This ID " +
74 | "allows receivers to identify the sink that published a specific message.",
75 | type = {DataType.STRING},
76 | optional = true,
77 | defaultValue = "null"),
78 | @Parameter(name = "key",
79 | description = "The key contains the values that are used to maintain ordering in a Kafka" +
80 | " partition.",
81 | type = {DataType.STRING},
82 | optional = true,
83 | defaultValue = "null"),
84 | @Parameter(name = "partition.no",
85 | description = "The partition number for the given topic. Only one partition ID can be " +
86 | "defined. If no value is specified for this parameter, the Kafka sink publishes " +
87 | "to the default partition of the topic (i.e., 0)",
88 | type = {DataType.INT},
89 | optional = true,
90 | defaultValue = "0"),
91 | @Parameter(name = "is.binary.message",
92 | description = "In order to send the binary events via kafkaMultiDCSink, it is required to set "
93 | + "this parameter to `true`.",
94 | type = {DataType.BOOL},
95 | optional = false,
96 | defaultValue = "null"),
97 | @Parameter(name = "optional.configuration",
98 | description = "This parameter contains all the other possible configurations that the " +
99 | "producer is created with. \n" +
100 | "e.g., `producer.type:async,batch.size:200`",
101 | optional = true,
102 | type = {DataType.STRING},
103 | defaultValue = "null")
104 | },
105 | examples = {
106 | @Example(
107 | syntax = "@App:name('TestExecutionPlan') \n" +
108 | "define stream FooStream (symbol string, price float, volume long); \n" +
109 | "@info(name = 'query1') \n" +
110 | "@sink("
111 | + "type='kafkaMultiDC', "
112 | + "topic='myTopic', "
113 | + "partition.no='0',"
114 | + "bootstrap.servers='host1:9092, host2:9092', "
115 | + "@map(type='xml'))" +
116 | "Define stream BarStream (symbol string, price float, volume long);\n" +
117 | "from FooStream select symbol, price, volume insert into BarStream;\n",
118 | description = "This query publishes to the default (i.e., 0th) partition of the brokers in " +
119 | "two data centers ")
120 | }
121 | )
122 | public class KafkaMultiDCSink extends KafkaSink {
123 | private static final Logger LOG = LogManager.getLogger(KafkaMultiDCSink.class);
124 | List> producers = new ArrayList<>();
125 | private String topic;
126 | private Integer partitionNo;
127 |
128 | @Override
129 | protected StateFactory init(StreamDefinition outputStreamDefinition, OptionHolder optionHolder,
130 | ConfigReader sinkConfigReader,
131 | SiddhiAppContext siddhiAppContext) {
132 | StateFactory stateStateFactory = super.init(outputStreamDefinition, optionHolder,
133 | sinkConfigReader, siddhiAppContext);
134 | topic = optionHolder.validateAndGetStaticValue(KAFKA_PUBLISH_TOPIC);
135 | partitionNo = Integer.parseInt(optionHolder.validateAndGetStaticValue(KAFKA_PARTITION_NO, "0"));
136 | if (bootstrapServers.split(",").length != 2) {
137 | throw new SiddhiAppValidationException("There should be two servers listed in 'bootstrap.servers' " +
138 | "configuration");
139 | }
140 |
141 | return stateStateFactory;
142 | }
143 |
144 | @Override
145 | public String[] getSupportedDynamicOptions() {
146 | return new String[]{KAFKA_MESSAGE_KEY};
147 | }
148 |
149 | @Override
150 | public void connect() throws ConnectionUnavailableException {
151 | Properties props = new Properties();
152 | props.put("acks", "all");
153 | props.put("retries", 0);
154 | props.put("batch.size", 16384);
155 | props.put("linger.ms", 1);
156 | props.put("buffer.memory", 33554432);
157 | props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
158 |
159 | if (!isBinaryMessage) {
160 | props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
161 | } else {
162 | props.put("value.serializer", "org.apache.kafka.common.serialization.ByteArraySerializer");
163 | }
164 |
165 | KafkaIOUtils.splitHeaderValues(optionalConfigs, props);
166 |
167 | String[] bootstrapServersList = bootstrapServers.split(",");
168 | for (int index = 0; index < bootstrapServersList.length; index++) {
169 | String server = bootstrapServersList[index].trim();
170 | props.put("bootstrap.servers", server);
171 | Producer producer = new KafkaProducer<>(props);
172 | producers.add(producer);
173 | LOG.info("Kafka producer created for Kafka cluster :{}", server);
174 | }
175 | }
176 |
177 |
178 | @Override
179 | public void publish(Object payload, DynamicOptions dynamicOptions, KafkaSinkState kafkaSinkState)
180 | throws ConnectionUnavailableException {
181 | String key = keyOption.getValue(dynamicOptions);
182 | Object payloadToSend = null;
183 | try {
184 | if (payload instanceof String) {
185 |
186 | // If it is required to send the message as string message.
187 | if (!isBinaryMessage) {
188 | StringBuilder strPayload = new StringBuilder();
189 | strPayload.append(sequenceId).append(SEQ_NO_HEADER_FIELD_SEPERATOR).
190 | append(kafkaSinkState.lastSentSequenceNo).append(SEQ_NO_HEADER_DELIMITER).
191 | append(payload.toString());
192 | payloadToSend = strPayload.toString();
193 | kafkaSinkState.lastSentSequenceNo.incrementAndGet();
194 |
195 | // If it is required to send 'xml`, 'json' or 'test' mapping payload as a byte stream through kafka.
196 | } else {
197 | byte[] byteEvents = payload.toString().getBytes("UTF-8");
198 | payloadToSend = getSequencedBinaryPayloadToSend(byteEvents, kafkaSinkState);
199 | kafkaSinkState.lastSentSequenceNo.incrementAndGet();
200 | }
201 | //if the received payload to send is binary.
202 | } else {
203 | byte[] byteEvents = ((ByteBuffer) payload).array();
204 | payloadToSend = getSequencedBinaryPayloadToSend(byteEvents, kafkaSinkState);
205 | kafkaSinkState.lastSentSequenceNo.incrementAndGet();
206 | }
207 | } catch (UnsupportedEncodingException e) {
208 | LOG.error("Error while converting the received string payload to byte[].", e);
209 | }
210 |
211 | for (Producer producer : producers) {
212 | try {
213 | producer.send(new ProducerRecord<>(topic, partitionNo, key, payloadToSend));
214 | } catch (Exception e) {
215 | LOG.error("Failed to publish the message to [topic] {}. Error: {}. Sequence Number " +
216 | ": {}", topic, e.getMessage(), kafkaSinkState.lastSentSequenceNo.get() - 1, e);
217 | }
218 | }
219 | }
220 |
221 | @Override
222 | public void disconnect() {
223 | for (Producer producer : producers) {
224 | producer.flush();
225 | producer.close();
226 | }
227 | producers.clear();
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/multidc/source/SourceSynchronizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 | package io.siddhi.extension.io.kafka.multidc.source;
19 |
20 | import io.siddhi.core.stream.input.source.SourceEventListener;
21 | import org.apache.logging.log4j.LogManager;
22 | import org.apache.logging.log4j.Logger;
23 |
24 | import java.util.ArrayList;
25 | import java.util.HashMap;
26 | import java.util.List;
27 | import java.util.Map;
28 | import java.util.Timer;
29 | import java.util.TimerTask;
30 | import java.util.TreeMap;
31 | import java.util.concurrent.atomic.AtomicBoolean;
32 |
33 |
34 | /**
35 | * The source Synchronize to merge events from two kafka source
36 | */
37 | public class SourceSynchronizer {
38 | private static final Logger LOG = LogManager.getLogger(SourceSynchronizer.class);
39 | private final SourceEventListener eventListener;
40 | boolean isEventGap = false;
41 | // Buffer events sorting by the sequence number.
42 | Map eventBuffer = new TreeMap<>();
43 | Map perSourceReceivedSeqNo = new HashMap<>();
44 | Timer flushBufferTimer = new Timer(true);
45 | String[] bootstrapServers = new String[2];
46 | List toRemoveSeqNos = new ArrayList<>();
47 | private Long lastConsumedSeqNo = -1L;
48 | private int maxBufferSize;
49 | private int bufferInterval;
50 | private AtomicBoolean isFlushTaskDue = new AtomicBoolean(false);
51 |
52 | public SourceSynchronizer(SourceEventListener eventListener, String[] bootstrapServers, int maxBufferSize,
53 | int bufferFlushInterval) {
54 | this.eventListener = eventListener;
55 | this.bootstrapServers[0] = bootstrapServers[0];
56 | this.bootstrapServers[1] = bootstrapServers[1];
57 | this.maxBufferSize = maxBufferSize;
58 | this.bufferInterval = bufferFlushInterval;
59 |
60 | perSourceReceivedSeqNo.put(bootstrapServers[0], -1L);
61 | perSourceReceivedSeqNo.put(bootstrapServers[1], -1L);
62 | }
63 |
64 | private synchronized void forceFlushBuffer(long flushTillSeqNo) {
65 | for (Map.Entry entry : eventBuffer.entrySet()) {
66 | Long sequenceNumber = entry.getKey();
67 | BufferValueHolder eventHolder = entry.getValue();
68 | if ((sequenceNumber > lastConsumedSeqNo) &&
69 | (sequenceNumber <= flushTillSeqNo)) {
70 | if (LOG.isDebugEnabled()) {
71 | LOG.debug("Updating the lastConsumedSeqNo={} as the event is forcefully flushed, " +
72 | "from the source {}", sequenceNumber, eventHolder.getSourceId());
73 | }
74 |
75 | if (!(sequenceNumber < lastConsumedSeqNo) &&
76 | (lastConsumedSeqNo != sequenceNumber + 1)) {
77 | LOG.warn("Events lost from sequence {} to {}", lastConsumedSeqNo + 1, sequenceNumber - 1);
78 | }
79 |
80 | lastConsumedSeqNo = sequenceNumber;
81 | toRemoveSeqNos.add(sequenceNumber);
82 | eventListener.onEvent(eventHolder.getEvent(), eventHolder.getObjects());
83 | }
84 | }
85 | toRemoveSeqNos.forEach(seqNo -> eventBuffer.remove(seqNo)); // To avoid concurrent modification.
86 | toRemoveSeqNos.clear();
87 | }
88 |
89 | private synchronized void flushBuffer() {
90 | if (LOG.isDebugEnabled()) {
91 | LOG.debug("Start flushing buffer");
92 | }
93 | for (Map.Entry entry : eventBuffer.entrySet()) {
94 | Long sequenceNumber = entry.getKey();
95 | BufferValueHolder eventHolder = entry.getValue();
96 | if (sequenceNumber <= lastConsumedSeqNo) {
97 | if (LOG.isDebugEnabled()) {
98 | LOG.debug("Message with sequence {} already received. Dropping the event from the buffer",
99 | sequenceNumber);
100 | }
101 | toRemoveSeqNos.add(sequenceNumber);
102 | continue;
103 | } else if (sequenceNumber == lastConsumedSeqNo + 1) {
104 | isEventGap = false;
105 | lastConsumedSeqNo++;
106 | if (LOG.isDebugEnabled()) {
107 | LOG.debug("Message with sequence {} flushed from buffer. Updating lastConsumedSeqNo={}",
108 | sequenceNumber, lastConsumedSeqNo);
109 | }
110 |
111 | toRemoveSeqNos.add(sequenceNumber);
112 | eventListener.onEvent(eventHolder.getEvent(), eventHolder.getObjects());
113 | } else {
114 | isEventGap = true;
115 | if (LOG.isDebugEnabled()) {
116 | LOG.debug("Gap detected while flushing the buffer. Flushed message sequence={}. Expected " +
117 | "sequence={}. Stop flushing the buffer.", sequenceNumber, lastConsumedSeqNo + 1);
118 | }
119 | break;
120 | }
121 | }
122 |
123 | toRemoveSeqNos.forEach(seqNo -> eventBuffer.remove(seqNo)); // To avoid concurrent modification.
124 | toRemoveSeqNos.clear();
125 | if (LOG.isDebugEnabled()) {
126 | LOG.debug("End flushing buffer");
127 | }
128 | }
129 |
130 | private synchronized void bufferEvent(String sourceId, long sequenceNumber, Object event, Object[] objects) {
131 | if (LOG.isDebugEnabled()) {
132 | LOG.debug("Buffering Event. SourceId={}, SequenceNumber={}", sourceId, sequenceNumber);
133 | }
134 |
135 | if (eventBuffer.size() >= maxBufferSize) {
136 | long flushTillSeq = Math.max(
137 | perSourceReceivedSeqNo.get(bootstrapServers[0]),
138 | perSourceReceivedSeqNo.get(bootstrapServers[1]));
139 | LOG.info("Buffer size exceeded. Force flushing events till the sequence {}", sequenceNumber);
140 | forceFlushBuffer(flushTillSeq);
141 | }
142 | eventBuffer.put(sequenceNumber, new BufferValueHolder(event, sourceId, objects));
143 | }
144 |
145 | public synchronized void onEvent(String sourceId, long sequenceNumber, Object event, Object[] objects) {
146 | perSourceReceivedSeqNo.put(sourceId, sequenceNumber);
147 |
148 | if (sequenceNumber <= lastConsumedSeqNo) {
149 | if (LOG.isDebugEnabled()) {
150 | LOG.debug("Message with sequence {} already received. Dropping the event from source {}:{}",
151 | sequenceNumber, sourceId, event);
152 | }
153 | } else if (sequenceNumber == lastConsumedSeqNo + 1) {
154 | lastConsumedSeqNo++;
155 | if (LOG.isDebugEnabled()) {
156 | LOG.debug("Message with sequence {} received from source {}. Updating lastConsumedSeqNo={}",
157 | sequenceNumber, sourceId, lastConsumedSeqNo);
158 | }
159 | eventListener.onEvent(event, objects);
160 |
161 | // Gap is filled by receiving the next expected sequence number
162 | if (!eventBuffer.isEmpty()) {
163 | flushBuffer();
164 | }
165 | } else { // Sequence number is greater than the expected sequence number
166 | if (isEventGap) {
167 | if (LOG.isDebugEnabled()) {
168 | LOG.debug("Message with sequence {} from source{}. Couldn't fill the gap, buffering the event.",
169 | sequenceNumber, sourceId);
170 | }
171 |
172 | bufferEvent(sourceId, sequenceNumber, event, objects);
173 | long flushTillSeq = Math.min(perSourceReceivedSeqNo.get(bootstrapServers[0]),
174 | perSourceReceivedSeqNo.get(bootstrapServers[1]));
175 | isEventGap = false;
176 | forceFlushBuffer(flushTillSeq);
177 | } else {
178 | if (LOG.isDebugEnabled()) {
179 | LOG.debug("Gap detected. Message with sequence {} received from source {}." +
180 | " Expected sequence number is {}. Starting buffering events",
181 | sequenceNumber, sourceId, lastConsumedSeqNo + 1);
182 | }
183 | isEventGap = true;
184 | bufferEvent(sourceId, sequenceNumber, event, objects);
185 |
186 | if (!isFlushTaskDue.get()) {
187 | flushBufferTimer.schedule(new BufferFlushTask(), bufferInterval);
188 | isFlushTaskDue.set(true);
189 | }
190 | }
191 | }
192 | }
193 |
194 | public synchronized Long getLastConsumedSeqNo() {
195 | return lastConsumedSeqNo;
196 | }
197 |
198 | public synchronized void setLastConsumedSeqNo(long seqNo) {
199 | this.lastConsumedSeqNo = seqNo;
200 | }
201 |
202 | static class BufferValueHolder {
203 | Object[] objects;
204 | private Object event;
205 | private String sourceId;
206 |
207 | BufferValueHolder(Object event, String sourceId, Object[] objects) {
208 | this.event = event;
209 | this.sourceId = sourceId;
210 | this.objects = objects;
211 | }
212 |
213 | Object[] getObjects() {
214 | return objects;
215 | }
216 |
217 | String getSourceId() {
218 | return sourceId;
219 | }
220 |
221 | public Object getEvent() {
222 | return event;
223 | }
224 | }
225 |
226 | class BufferFlushTask extends TimerTask {
227 | private final Logger log = LogManager.getLogger(BufferFlushTask.class);
228 |
229 | @Override
230 | public synchronized void run() {
231 | isFlushTaskDue.set(false);
232 | long flushTillSeq = Math.max(perSourceReceivedSeqNo.get(bootstrapServers[0]),
233 | perSourceReceivedSeqNo.get(bootstrapServers[1]));
234 | if (log.isDebugEnabled()) {
235 | log.debug("Executing the buffer flushing task. Flushing buffers till {}", flushTillSeq);
236 | }
237 | forceFlushBuffer(flushTillSeq);
238 | }
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/sink/KafkaReplayRequestSink.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 | package io.siddhi.extension.io.kafka.sink;
19 |
20 | import io.siddhi.annotation.Example;
21 | import io.siddhi.annotation.Extension;
22 | import io.siddhi.annotation.Parameter;
23 | import io.siddhi.annotation.util.DataType;
24 | import io.siddhi.core.config.SiddhiAppContext;
25 | import io.siddhi.core.event.Event;
26 | import io.siddhi.core.exception.ConnectionUnavailableException;
27 | import io.siddhi.core.stream.ServiceDeploymentInfo;
28 | import io.siddhi.core.stream.output.sink.Sink;
29 | import io.siddhi.core.util.config.ConfigReader;
30 | import io.siddhi.core.util.snapshot.state.State;
31 | import io.siddhi.core.util.snapshot.state.StateFactory;
32 | import io.siddhi.core.util.transport.DynamicOptions;
33 | import io.siddhi.core.util.transport.OptionHolder;
34 | import io.siddhi.extension.io.kafka.Constants;
35 | import io.siddhi.extension.io.kafka.util.KafkaReplayResponseSourceRegistry;
36 | import io.siddhi.query.api.definition.StreamDefinition;
37 |
38 | /**
39 | * This class implements a Kafka Replay Request Sink
40 | */
41 | @Extension(
42 | name = "kafka-replay-request",
43 | namespace = "sink",
44 | description = "This sink is used to request replay of specific range of events on a specified partition of a " +
45 | "topic.",
46 | parameters = {
47 | @Parameter(name = "sink.id",
48 | description = "a unique SINK_ID should be set. This sink id will be used to match with the " +
49 | "appropriate kafka-replay-response source",
50 | type = {DataType.STRING})
51 | },
52 | examples = {
53 | @Example(
54 | syntax = "@App:name('TestKafkaReplay')\n" +
55 | "\n" +
56 | "@sink(type='kafka-replay-request', sink.id='1')\n" +
57 | "define stream BarStream (topicForReplay string, partitionForReplay string, " +
58 | "startOffset string, endOffset string);\n" +
59 | "\n" +
60 | "@info(name = 'query1')\n" +
61 | "@source(type='kafka-replay-response', group.id='group', threading.option=" +
62 | "'single.thread', bootstrap.servers='localhost:9092', sink.id='1',\n" +
63 | "@map(type='json'))\n" +
64 | "Define stream FooStream (symbol string, amount double);\n" +
65 | "\n" +
66 | "@sink(type='log')\n" +
67 | "Define stream logStream(symbol string, amount double);\n" +
68 | "\n" +
69 | "from FooStream select * insert into logStream;",
70 | description = "In this app we can send replay request events into BarStream and observe the " +
71 | "replayed events in the logStream")
72 | }
73 | )
74 |
75 | public class KafkaReplayRequestSink extends Sink {
76 | private String sinkID;
77 |
78 | @Override
79 | public Class[] getSupportedInputEventClasses() {
80 | return new Class[]{String.class, Event.class};
81 | }
82 |
83 | @Override
84 | protected ServiceDeploymentInfo exposeServiceDeploymentInfo() {
85 | return null;
86 | }
87 |
88 | @Override
89 | public String[] getSupportedDynamicOptions() {
90 | return new String[0];
91 | }
92 |
93 | @Override
94 | protected StateFactory init(StreamDefinition outputStreamDefinition, OptionHolder optionHolder,
95 | ConfigReader sinkConfigReader, SiddhiAppContext siddhiAppContext) {
96 | this.sinkID = optionHolder.validateAndGetOption(Constants.SINK_ID).getValue();
97 | return null;
98 | }
99 |
100 | @Override
101 | public void publish(Object payload, DynamicOptions dynamicOptions, State state)
102 | throws ConnectionUnavailableException {
103 | String partitionForReplay;
104 | String startOffset;
105 | String endOffset;
106 | String replayTopic;
107 | Object[] replayParams;
108 | if (payload instanceof Event[]) {
109 | replayParams = ((Event[]) payload)[0].getData();
110 | } else if (payload instanceof Event) {
111 | replayParams = ((Event) payload).getData();
112 | } else {
113 | throw new ConnectionUnavailableException("Unknown type");
114 | }
115 | replayTopic = (String) replayParams[0];
116 | partitionForReplay = (String) replayParams[1];
117 | startOffset = (String) replayParams[2];
118 | endOffset = (String) replayParams[3];
119 | KafkaReplayResponseSourceRegistry.getInstance().getKafkaReplayResponseSource(sinkID)
120 | .onReplayRequest(partitionForReplay, startOffset, endOffset, replayTopic);
121 | }
122 |
123 | @Override
124 | public void connect() throws ConnectionUnavailableException {
125 |
126 | }
127 |
128 | @Override
129 | public void disconnect() {
130 |
131 | }
132 |
133 | @Override
134 | public void destroy() {
135 |
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/source/ConsumerKafkaGroup.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka.source;
20 |
21 | import io.siddhi.core.stream.input.source.SourceEventListener;
22 | import io.siddhi.extension.io.kafka.metrics.SourceMetrics;
23 | import org.apache.logging.log4j.LogManager;
24 | import org.apache.logging.log4j.Logger;
25 |
26 | import java.util.ArrayList;
27 | import java.util.Arrays;
28 | import java.util.List;
29 | import java.util.Properties;
30 | import java.util.concurrent.ExecutorService;
31 | import java.util.concurrent.Future;
32 |
33 | /**
34 | * This processes the Kafka messages using a thread pool.
35 | */
36 | public class ConsumerKafkaGroup {
37 | private static final Logger LOG = LogManager.getLogger(ConsumerKafkaGroup.class);
38 | private final String topics[];
39 | private final String partitions[];
40 | private final Properties props;
41 | private List kafkaConsumerThreadList = new ArrayList<>();
42 | private ExecutorService executorService;
43 | private String threadingOption;
44 | private boolean isBinaryMessage;
45 | private KafkaSource.KafkaSourceState kafkaSourceState;
46 | private List> futureList = new ArrayList<>();
47 |
48 | ConsumerKafkaGroup(String[] topics, String[] partitions, Properties props, String threadingOption,
49 | ExecutorService executorService, boolean isBinaryMessage, boolean enableOffsetCommit,
50 | boolean enableAsyncCommit, SourceEventListener sourceEventListener,
51 | String[] requiredProperties, SourceMetrics metrics) {
52 | this.threadingOption = threadingOption;
53 | this.topics = topics;
54 | this.partitions = partitions;
55 | this.props = props;
56 | this.executorService = executorService;
57 | this.isBinaryMessage = isBinaryMessage;
58 |
59 | if (KafkaSource.SINGLE_THREADED.equals(threadingOption)) {
60 | KafkaConsumerThread kafkaConsumerThread =
61 | new KafkaConsumerThread(sourceEventListener, topics, partitions, props,
62 | false, isBinaryMessage, enableOffsetCommit, enableAsyncCommit,
63 | requiredProperties, metrics);
64 | kafkaConsumerThreadList.add(kafkaConsumerThread);
65 | LOG.info("Kafka Consumer thread starting to listen on topic(s): {} with partition/s: {}",
66 | Arrays.toString(topics), Arrays.toString(partitions));
67 | } else if (KafkaSource.TOPIC_WISE.equals(threadingOption)) {
68 | for (String topic : topics) {
69 | KafkaConsumerThread kafkaConsumerThread =
70 | new KafkaConsumerThread(sourceEventListener, new String[]{topic}, partitions, props,
71 | false, isBinaryMessage, enableOffsetCommit, enableAsyncCommit,
72 | requiredProperties, metrics);
73 | kafkaConsumerThreadList.add(kafkaConsumerThread);
74 | LOG.info("Kafka Consumer thread starting to listen on topic: {} with partition/s: {}", topic,
75 | Arrays.toString(partitions));
76 | }
77 | } else if (KafkaSource.PARTITION_WISE.equals(threadingOption)) {
78 | for (String topic : topics) {
79 | for (String partition : partitions) {
80 | KafkaConsumerThread kafkaConsumerThread =
81 | new KafkaConsumerThread(sourceEventListener, new String[]{topic},
82 | new String[]{partition}, props, true,
83 | isBinaryMessage, enableOffsetCommit, enableAsyncCommit, requiredProperties,
84 | metrics);
85 | kafkaConsumerThreadList.add(kafkaConsumerThread);
86 | LOG.info("Kafka Consumer thread starting to listen on topic: {} with partition: {}", topic,
87 | partition);
88 | }
89 | }
90 | }
91 | }
92 |
93 | void pause() {
94 | kafkaConsumerThreadList.forEach(KafkaConsumerThread::pause);
95 | }
96 |
97 | void resume() {
98 | kafkaConsumerThreadList.forEach(KafkaConsumerThread::resume);
99 | }
100 |
101 | void restoreState() {
102 | kafkaConsumerThreadList.forEach(kafkaConsumerThread -> kafkaConsumerThread.restore());
103 | }
104 |
105 | void shutdown() {
106 | kafkaConsumerThreadList.forEach(KafkaConsumerThread::shutdownConsumer);
107 | futureList.forEach(future -> {
108 | if (!future.isCancelled()) {
109 | future.cancel(true);
110 | }
111 | });
112 | }
113 |
114 | void run() {
115 | try {
116 | for (KafkaConsumerThread consumerThread : kafkaConsumerThreadList) {
117 | futureList.add(executorService.submit(consumerThread));
118 | }
119 | } catch (Throwable t) {
120 | LOG.error("Error while creating KafkaConsumerThread for topic(s): {}", Arrays.toString(topics), t);
121 | }
122 | }
123 |
124 | public void setKafkaSourceState(KafkaSource.KafkaSourceState kafkaSourceState) {
125 | this.kafkaSourceState = kafkaSourceState;
126 | for (KafkaConsumerThread consumer : kafkaConsumerThreadList) {
127 | consumer.setKafkaSourceState(kafkaSourceState);
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/source/KafkaReplayResponseSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 | package io.siddhi.extension.io.kafka.source;
19 |
20 |
21 | /**
22 | * This class implements a Kafka source to receive events from a kafka cluster.
23 | */
24 |
25 | import io.siddhi.annotation.Example;
26 | import io.siddhi.annotation.Extension;
27 | import io.siddhi.annotation.Parameter;
28 | import io.siddhi.annotation.util.DataType;
29 | import io.siddhi.core.config.SiddhiAppContext;
30 | import io.siddhi.core.exception.ConnectionUnavailableException;
31 | import io.siddhi.core.exception.SiddhiAppRuntimeException;
32 | import io.siddhi.core.stream.input.source.SourceEventListener;
33 | import io.siddhi.core.util.config.ConfigReader;
34 | import io.siddhi.core.util.snapshot.state.StateFactory;
35 | import io.siddhi.core.util.transport.OptionHolder;
36 | import io.siddhi.extension.io.kafka.Constants;
37 | import io.siddhi.extension.io.kafka.util.KafkaReplayResponseSourceRegistry;
38 |
39 | import java.util.ArrayList;
40 | import java.util.List;
41 | import java.util.concurrent.ExecutorService;
42 | import java.util.concurrent.Future;
43 |
44 | /**
45 | * This source is used to listen to replayed events requested from kafka-replay-request sink
46 | */
47 | @Extension(
48 | name = "kafka-replay-response",
49 | namespace = "source",
50 | description = "This source is used to listen to replayed events requested from kafka-replay-request sink",
51 | parameters = {
52 | @Parameter(name = "bootstrap.servers",
53 | description = "This specifies the list of Kafka servers to which the Kafka source " +
54 | "must listen. This list can be provided as a set of comma-separated values.\n" +
55 | "e.g., `localhost:9092,localhost:9093`",
56 | type = {DataType.STRING}),
57 | @Parameter(name = "group.id",
58 | description = "This is an ID to identify the Kafka source group. The group ID ensures " +
59 | "that sources with the same topic and partition that are in the same group do not" +
60 | " receive the same event.",
61 | type = {DataType.STRING}),
62 | @Parameter(name = "threading.option",
63 | description = " This specifies whether the Kafka source is to be run on a single thread," +
64 | " or in multiple threads based on a condition. Possible values are as follows:\n" +
65 | "`single.thread`: To run the Kafka source on a single thread.\n" +
66 | "`topic.wise`: To use a separate thread per topic.\n" +
67 | "`partition.wise`: To use a separate thread per partition.",
68 | type = {DataType.STRING}),
69 | @Parameter(
70 | name = "sink.id",
71 | description = "a unique SINK_ID .",
72 | type = {DataType.INT}),
73 | },
74 | examples = {
75 | @Example(
76 | syntax = "@App:name('TestKafkaReplay')\n" +
77 | "\n" +
78 | "@sink(type='kafka-replay-request', sink.id='1')\n" +
79 | "define stream BarStream (topicForReplay string, partitionForReplay string, " +
80 | "startOffset string, endOffset string);\n" +
81 | "\n" +
82 | "@info(name = 'query1')\n" +
83 | "@source(type='kafka-replay-response', group.id='group', threading.option=" +
84 | "'single.thread', bootstrap.servers='localhost:9092', sink.id='1',\n" +
85 | "@map(type='json'))\n" +
86 | "Define stream FooStream (symbol string, amount double);\n" +
87 | "\n" +
88 | "@sink(type='log')\n" +
89 | "Define stream logStream(symbol string, amount double);\n" +
90 | "\n" +
91 | "from FooStream select * insert into logStream;",
92 | description = "In this app we can send replay request events into BarStream and observe the " +
93 | "replayed events in the logStream")
94 | }
95 | )
96 | public class KafkaReplayResponseSource extends KafkaSource {
97 | private String sinkId;
98 | private List> futureList = new ArrayList<>();
99 | private List kafkaReplayThreadList = new ArrayList<>();
100 |
101 | @Override
102 | public StateFactory init(SourceEventListener sourceEventListener, OptionHolder optionHolder,
103 | String[] requiredProperties, ConfigReader configReader,
104 | SiddhiAppContext siddhiAppContext) {
105 | this.siddhiAppContext = siddhiAppContext;
106 | this.optionHolder = optionHolder;
107 | this.requiredProperties = requiredProperties.clone();
108 | this.sourceEventListener = sourceEventListener;
109 | if (configReader != null) {
110 | bootstrapServers = configReader.readConfig(ADAPTOR_SUBSCRIBER_ZOOKEEPER_CONNECT_SERVERS,
111 | optionHolder.validateAndGetStaticValue(ADAPTOR_SUBSCRIBER_ZOOKEEPER_CONNECT_SERVERS));
112 | groupID = configReader.readConfig(ADAPTOR_SUBSCRIBER_GROUP_ID,
113 | optionHolder.validateAndGetStaticValue(ADAPTOR_SUBSCRIBER_GROUP_ID));
114 | threadingOption = configReader.readConfig(THREADING_OPTION,
115 | optionHolder.validateAndGetStaticValue(THREADING_OPTION));
116 | seqEnabled = configReader.readConfig(SEQ_ENABLED,
117 | optionHolder.validateAndGetStaticValue(SEQ_ENABLED, "false"))
118 | .equalsIgnoreCase("true");
119 | optionalConfigs = configReader.readConfig(ADAPTOR_OPTIONAL_CONFIGURATION_PROPERTIES,
120 | optionHolder.validateAndGetStaticValue(ADAPTOR_OPTIONAL_CONFIGURATION_PROPERTIES, null));
121 | isBinaryMessage = Boolean.parseBoolean(configReader.readConfig(IS_BINARY_MESSAGE,
122 | optionHolder.validateAndGetStaticValue(IS_BINARY_MESSAGE, "false")));
123 | enableOffsetCommit = Boolean.parseBoolean(configReader.readConfig(ADAPTOR_ENABLE_OFFSET_COMMIT,
124 | optionHolder.validateAndGetStaticValue(ADAPTOR_ENABLE_OFFSET_COMMIT, "true")));
125 | enableAsyncCommit = Boolean.parseBoolean(configReader.readConfig(ADAPTOR_ENABLE_ASYNC_COMMIT,
126 | optionHolder.validateAndGetStaticValue(ADAPTOR_ENABLE_ASYNC_COMMIT, "true")));
127 |
128 | } else {
129 | bootstrapServers = optionHolder.validateAndGetStaticValue(ADAPTOR_SUBSCRIBER_ZOOKEEPER_CONNECT_SERVERS);
130 | groupID = optionHolder.validateAndGetStaticValue(ADAPTOR_SUBSCRIBER_GROUP_ID);
131 | threadingOption = optionHolder.validateAndGetStaticValue(THREADING_OPTION);
132 | optionalConfigs = optionHolder.validateAndGetStaticValue(ADAPTOR_OPTIONAL_CONFIGURATION_PROPERTIES, null);
133 | isBinaryMessage = Boolean.parseBoolean(optionHolder.validateAndGetStaticValue(IS_BINARY_MESSAGE,
134 | "false"));
135 | enableOffsetCommit = Boolean.parseBoolean(optionHolder.
136 | validateAndGetStaticValue(ADAPTOR_ENABLE_OFFSET_COMMIT, "true"));
137 | enableAsyncCommit = Boolean.parseBoolean(optionHolder.validateAndGetStaticValue(ADAPTOR_ENABLE_ASYNC_COMMIT,
138 | "true"));
139 | }
140 | seqEnabled = optionHolder.validateAndGetStaticValue(SEQ_ENABLED, "false").equalsIgnoreCase("true");
141 | optionalConfigs = optionHolder.validateAndGetStaticValue(ADAPTOR_OPTIONAL_CONFIGURATION_PROPERTIES, null);
142 | isBinaryMessage = Boolean.parseBoolean(optionHolder.validateAndGetStaticValue(IS_BINARY_MESSAGE,
143 | "false"));
144 | enableOffsetCommit = Boolean.parseBoolean(optionHolder.validateAndGetStaticValue(ADAPTOR_ENABLE_OFFSET_COMMIT,
145 | "true"));
146 | enableAsyncCommit = Boolean.parseBoolean(optionHolder.validateAndGetStaticValue(ADAPTOR_ENABLE_ASYNC_COMMIT,
147 | "true"));
148 | this.sinkId = optionHolder.validateAndGetStaticValue(Constants.SINK_ID);
149 | KafkaReplayResponseSourceRegistry.getInstance().putKafkaReplayResponseSource(sinkId, this);
150 | return () -> new KafkaSourceState(seqEnabled);
151 | }
152 |
153 | @Override
154 | public void connect(ConnectionCallback connectionCallback, KafkaSourceState kafkaSourceState) {
155 | }
156 |
157 | public void onReplayRequest(String partitionForReplay, String startOffset, String endOffset, String replayTopic)
158 | throws ConnectionUnavailableException {
159 | try {
160 | String[] partitionAsListForReplay = new String[]{partitionForReplay};
161 | ExecutorService executorService = siddhiAppContext.getExecutorService();
162 | KafkaReplayThread kafkaReplayThread =
163 | new KafkaReplayThread(sourceEventListener, new String[]{replayTopic}, partitionAsListForReplay,
164 | KafkaSource.createConsumerConfig(bootstrapServers, groupID, optionalConfigs,
165 | isBinaryMessage, enableOffsetCommit),
166 | false, isBinaryMessage, enableOffsetCommit,
167 | enableAsyncCommit, requiredProperties, Integer.parseInt(startOffset),
168 | Integer.parseInt(endOffset), futureList.size(), sinkId);
169 | kafkaReplayThreadList.add(kafkaReplayThread);
170 | futureList.add(executorService.submit(kafkaReplayThread));
171 | } catch (SiddhiAppRuntimeException e) {
172 | throw e;
173 | } catch (Throwable e) {
174 | throw new ConnectionUnavailableException("Error when initiating connection with Kafka server: " +
175 | bootstrapServers + " in Siddhi App: " + siddhiAppContext.getName(), e);
176 | }
177 | }
178 |
179 | public void onReplayFinish(int threadId) {
180 | kafkaReplayThreadList.get(threadId).shutdownConsumer();
181 | Future future = futureList.get(threadId);
182 | if (!future.isCancelled()) {
183 | future.cancel(true);
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/source/KafkaReplayThread.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka.source;
20 |
21 | import io.siddhi.core.stream.input.source.SourceEventListener;
22 | import io.siddhi.extension.io.kafka.util.KafkaReplayResponseSourceRegistry;
23 | import org.apache.kafka.clients.consumer.ConsumerRecord;
24 |
25 | import java.util.Properties;
26 |
27 | /**
28 | * This runnable processes each Kafka message and sends it to siddhi.
29 | */
30 | public class KafkaReplayThread extends KafkaConsumerThread {
31 | private int startOffset;
32 | private int endOffset;
33 | private int threadId;
34 | private String sinkId;
35 |
36 | KafkaReplayThread(SourceEventListener sourceEventListener, String[] topics, String[] partitions, Properties props,
37 | boolean isPartitionWiseThreading, boolean isBinaryMessage, boolean enableOffsetCommit,
38 | boolean enableAsyncCommit, String[] requiredProperties, int startOffset, int endOffset,
39 | int threadId, String sinkId) {
40 | super(sourceEventListener, topics, partitions, props, isPartitionWiseThreading, isBinaryMessage,
41 | enableOffsetCommit, enableAsyncCommit, requiredProperties, null);
42 | this.threadId = threadId;
43 | this.sinkId = sinkId;
44 | this.startOffset = startOffset;
45 | this.endOffset = endOffset;
46 | this.isReplayThread = true;
47 | }
48 |
49 | @Override
50 | void seekToRequiredOffset() {
51 | consumer.seekToBeginning(partitionsList);
52 | }
53 |
54 | @Override
55 | boolean isRecordAfterStartOffset(ConsumerRecord record) {
56 | return record.offset() >= startOffset;
57 | }
58 |
59 | @Override
60 | boolean endReplay(ConsumerRecord record) {
61 | if (record.offset() >= endOffset) {
62 | KafkaReplayResponseSourceRegistry.getInstance().getKafkaReplayResponseSource(sinkId)
63 | .onReplayFinish(threadId);
64 | return true;
65 | } else {
66 | return false;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/component/src/main/java/io/siddhi/extension/io/kafka/util/KafkaReplayResponseSourceRegistry.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 | package io.siddhi.extension.io.kafka.util;
19 |
20 | import io.siddhi.extension.io.kafka.source.KafkaReplayResponseSource;
21 |
22 | import java.util.Collections;
23 | import java.util.HashMap;
24 | import java.util.Map;
25 |
26 | /**
27 | * a class to register KafkaReplayResponseSource with respective sink.id or source.id. Used by KafkaReplayRequestSink
28 | * to push responses
29 | */
30 | public class KafkaReplayResponseSourceRegistry {
31 | private static KafkaReplayResponseSourceRegistry instance = new KafkaReplayResponseSourceRegistry();
32 | private Map kafkaReplayResponseSourceHashMap = Collections.synchronizedMap(
33 | new HashMap<>());
34 |
35 | private KafkaReplayResponseSourceRegistry() {}
36 |
37 | public static KafkaReplayResponseSourceRegistry getInstance() {
38 | return instance;
39 | }
40 |
41 | public void putKafkaReplayResponseSource(String key, KafkaReplayResponseSource source) {
42 | kafkaReplayResponseSourceHashMap.put(key, source);
43 | }
44 |
45 | public KafkaReplayResponseSource getKafkaReplayResponseSource(String key) {
46 | return kafkaReplayResponseSourceHashMap.get(key);
47 | }
48 | public void removeKafkaReplayResponseSource(String key) {
49 | kafkaReplayResponseSourceHashMap.remove(key);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/component/src/test/java/io/siddhi/extension/io/kafka/KafkaTestUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka;
20 |
21 | import kafka.admin.AdminUtils;
22 | import kafka.admin.RackAwareMode;
23 | import kafka.common.TopicExistsException;
24 | import kafka.server.KafkaConfig;
25 | import kafka.server.KafkaServerStartable;
26 | import kafka.utils.ZKStringSerializer$;
27 | import kafka.utils.ZkUtils;
28 | import org.I0Itec.zkclient.ZkClient;
29 | import org.I0Itec.zkclient.ZkConnection;
30 | import org.apache.commons.io.FileUtils;
31 | import org.apache.curator.test.TestingServer;
32 | import org.apache.kafka.clients.producer.KafkaProducer;
33 | import org.apache.kafka.clients.producer.Producer;
34 | import org.apache.kafka.clients.producer.ProducerRecord;
35 | import org.apache.logging.log4j.LogManager;
36 | import org.apache.logging.log4j.Logger;
37 |
38 | import java.io.File;
39 | import java.io.IOException;
40 | import java.util.Properties;
41 |
42 | /**
43 | * Class defining the Constant for Kafka Test cases.
44 | */
45 | public class KafkaTestUtil {
46 | public static final String ZK_SERVER_CON_STRING = "localhost:2181";
47 | public static final String ZK_SERVER2_CON_STRING = "localhost:2182";
48 | private static final Logger log = LogManager.getLogger(KafkaTestUtil.class);
49 | private static final String kafkaLogDir = "tmp_kafka_dir";
50 | private static final String kafkaLogDir2 = "tmp_kafka_dir2";
51 | private static final long CLEANER_BUFFER_SIZE = 2 * 1024 * 1024L;
52 | private static TestingServer zkTestServer;
53 | private static TestingServer zkTestServer2;
54 | private static KafkaServerStartable kafkaServer;
55 | private static KafkaServerStartable kafkaServer2;
56 |
57 | public static void cleanLogDir() {
58 | try {
59 | File f = new File(kafkaLogDir);
60 | FileUtils.deleteDirectory(f);
61 | } catch (IOException e) {
62 | log.error("Failed to clean up: " + e);
63 | }
64 | }
65 |
66 | public static void cleanLogDir2() {
67 | try {
68 | File f = new File(kafkaLogDir2);
69 | FileUtils.deleteDirectory(f);
70 | } catch (IOException e) {
71 | log.error("Failed to clean up: " + e);
72 | }
73 | }
74 |
75 | //---- private methods --------
76 | public static void setupKafkaBroker() {
77 | try {
78 | log.info("#############################################################################################");
79 | log.info("################################# ZOOKEEPER STARTED ######################################");
80 | log.info("#############################################################################################");
81 | // mock zookeeper
82 | zkTestServer = new TestingServer(2181);
83 | // mock kafka
84 | Properties props = new Properties();
85 | props.put("broker.id", "0");
86 | props.put("host.name", "localhost");
87 | props.put("port", "9092");
88 | props.put("log.dir", kafkaLogDir);
89 | props.put("zookeeper.connect", zkTestServer.getConnectString());
90 | props.put("replica.socket.timeout.ms", "30000");
91 | props.put("delete.topic.enable", "true");
92 | props.put("log.cleaner.dedupe.buffer.size", CLEANER_BUFFER_SIZE);
93 | KafkaConfig config = new KafkaConfig(props);
94 | kafkaServer = new KafkaServerStartable(config);
95 | kafkaServer.startup();
96 | } catch (Exception e) {
97 | log.error("Error running local Kafka broker / Zookeeper", e);
98 | }
99 | }
100 |
101 | public static void setupKafkaBroker2() {
102 | try {
103 | log.info("#############################################################################################");
104 | log.info("################################# ZOOKEEPER 2 STARTED ####################################");
105 | log.info("#############################################################################################");
106 | // mock zookeeper
107 | zkTestServer2 = new TestingServer(2182);
108 | // mock kafka
109 | Properties props = new Properties();
110 | props.put("broker.id", "1");
111 | props.put("host.name", "localhost");
112 | props.put("port", "9093");
113 | props.put("log.dir", kafkaLogDir2);
114 | props.put("zookeeper.connect", zkTestServer2.getConnectString());
115 | props.put("replica.socket.timeout.ms", "30000");
116 | props.put("delete.topic.enable", "true");
117 | props.put("log.cleaner.dedupe.buffer.size", CLEANER_BUFFER_SIZE);
118 | KafkaConfig config = new KafkaConfig(props);
119 | kafkaServer2 = new KafkaServerStartable(config);
120 | kafkaServer2.startup();
121 |
122 | } catch (Exception e) {
123 | log.error("Error running local Kafka broker 2", e);
124 | }
125 | }
126 |
127 | public static void stopKafkaBroker2() {
128 | log.info("#############################################################################################");
129 | log.info("################################# ZOOKEEPER 2 STOPPED ####################################");
130 | log.info("#############################################################################################");
131 | try {
132 | if (kafkaServer2 != null) {
133 | kafkaServer2.shutdown();
134 | kafkaServer2.awaitShutdown();
135 | }
136 | Thread.sleep(5000);
137 | if (zkTestServer2 != null) {
138 | zkTestServer2.stop();
139 | }
140 | Thread.sleep(5000);
141 | cleanLogDir2();
142 | } catch (InterruptedException e) {
143 | log.error(e.getMessage(), e);
144 | } catch (IOException e) {
145 | log.error("Error shutting down 2nd Kafka broker / Zookeeper", e);
146 | }
147 | }
148 |
149 | public static void stopKafkaBroker() {
150 | log.info("#############################################################################################");
151 | log.info("################################# ZOOKEEPER STOPPED ######################################");
152 | log.info("#############################################################################################");
153 | try {
154 | if (kafkaServer != null) {
155 | kafkaServer.shutdown();
156 | kafkaServer.awaitShutdown();
157 | }
158 | Thread.sleep(500);
159 | if (zkTestServer != null) {
160 | zkTestServer.stop();
161 | }
162 | Thread.sleep(500);
163 | cleanLogDir();
164 | } catch (InterruptedException e) {
165 | log.error(e.getMessage(), e);
166 | } catch (IOException e) {
167 | log.error("Error shutting down Kafka broker / Zookeeper", e);
168 | }
169 | }
170 |
171 |
172 | public static void createTopic(String topics[], int numOfPartitions) {
173 | createTopic(ZK_SERVER_CON_STRING, topics, numOfPartitions);
174 | }
175 |
176 | public static void createTopic(String connectionString, String topics[], int numOfPartitions) {
177 | ZkClient zkClient = new ZkClient(connectionString, 30000, 30000, ZKStringSerializer$.MODULE$);
178 | ZkConnection zkConnection = new ZkConnection(connectionString);
179 | ZkUtils zkUtils = new ZkUtils(zkClient, zkConnection, false);
180 | for (String topic : topics) {
181 | try {
182 | AdminUtils.createTopic(zkUtils, topic, numOfPartitions, 1, new Properties(),
183 | RackAwareMode.Enforced$.MODULE$);
184 | } catch (TopicExistsException e) {
185 | log.warn("topic exists for: " + topic);
186 | }
187 | }
188 | zkClient.close();
189 | }
190 |
191 | public static void deleteTopic(String topics[]) {
192 | deleteTopic("localhost:2181", topics);
193 | }
194 |
195 | public static void deleteTopic(String connectionString, String topics[]) {
196 | ZkClient zkClient = new ZkClient(connectionString, 30000, 30000, ZKStringSerializer$.MODULE$);
197 | ZkConnection zkConnection = new ZkConnection(connectionString);
198 | ZkUtils zkUtils = new ZkUtils(zkClient, zkConnection, false);
199 | for (String topic : topics) {
200 | AdminUtils.deleteTopic(zkUtils, topic);
201 | }
202 | zkClient.close();
203 | }
204 |
205 | public static void kafkaPublisher(String topics[], int numOfPartitions, int numberOfEventsPerTopic, boolean
206 | publishWithPartition, String bootstrapServers, boolean isXML) {
207 | kafkaPublisher(topics, numOfPartitions, numberOfEventsPerTopic, 1000, publishWithPartition,
208 | bootstrapServers, isXML);
209 | }
210 |
211 | public static void kafkaPublisher(String topics[], int numOfPartitions, int numberOfEventsPerTopic, long sleep,
212 | boolean publishWithPartition, String bootstrapServers, boolean isXML) {
213 | Properties props = new Properties();
214 | if (null == bootstrapServers) {
215 | props.put("bootstrap.servers", "localhost:9092");
216 | } else {
217 | props.put("bootstrap.servers", bootstrapServers);
218 | }
219 | props.put("acks", "all");
220 | props.put("retries", 0);
221 | props.put("batch.size", 16384);
222 | props.put("linger.ms", 1);
223 | props.put("buffer.memory", 33559000);
224 | props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
225 | props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
226 | Producer producer = new KafkaProducer(props);
227 | for (String topic : topics) {
228 | for (int i = 0; i < numberOfEventsPerTopic; i++) {
229 | String msg;
230 | if (isXML) {
231 | msg = ""
232 | + ""
233 | + "" + topic + ""
234 | + "12.5"
235 | + "" + i + ""
236 | + ""
237 | + "";
238 | } else {
239 | msg = "symbol:\"" + topic + "\",\nprice:12.5,\nvolume:" + i;
240 | }
241 |
242 | try {
243 | Thread.sleep(sleep);
244 | } catch (InterruptedException e) {
245 | }
246 | if (numOfPartitions > 1 || publishWithPartition) {
247 | log.info("producing: " + msg + " into partition: " + (i % numOfPartitions));
248 | producer.send(new ProducerRecord<>(topic, (i % numOfPartitions),
249 | System.currentTimeMillis(), null, msg));
250 | } else {
251 | log.info("producing: " + msg);
252 | producer.send(new ProducerRecord<>(topic, null, System.currentTimeMillis(), null, msg));
253 | }
254 | }
255 | }
256 | producer.flush();
257 | producer.close();
258 | try {
259 | Thread.sleep(2000);
260 | } catch (InterruptedException e) {
261 | log.error("Thread sleep failed", e);
262 | }
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/component/src/test/java/io/siddhi/extension/io/kafka/UnitTestAppender.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019 WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | *
18 | */
19 |
20 | package io.siddhi.extension.io.kafka;
21 |
22 | import org.apache.logging.log4j.core.Appender;
23 | import org.apache.logging.log4j.core.Core;
24 | import org.apache.logging.log4j.core.Filter;
25 | import org.apache.logging.log4j.core.LogEvent;
26 | import org.apache.logging.log4j.core.appender.AbstractAppender;
27 | import org.apache.logging.log4j.core.config.plugins.Plugin;
28 | import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
29 | import org.apache.logging.log4j.core.config.plugins.PluginElement;
30 | import org.apache.logging.log4j.core.config.plugins.PluginFactory;
31 | import org.mvel2.util.StringAppender;
32 |
33 | @Plugin(name = "UnitTestAppender",
34 | category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
35 | public class UnitTestAppender extends AbstractAppender {
36 |
37 | private StringAppender messages = new StringAppender();
38 |
39 | public UnitTestAppender(String name, Filter filter) {
40 |
41 | super(name, filter, null);
42 | }
43 |
44 | @PluginFactory
45 | public static UnitTestAppender createAppender(
46 | @PluginAttribute("name") String name,
47 | @PluginElement("Filter") Filter filter) {
48 |
49 | return new UnitTestAppender(name, filter);
50 | }
51 |
52 | public String getMessages() {
53 |
54 | String results = messages.toString();
55 | if (results.isEmpty()) {
56 | return null;
57 | }
58 | return results;
59 | }
60 |
61 | @Override
62 | public void append(LogEvent event) {
63 |
64 | messages.append(event.getMessage().getFormattedMessage());
65 | }
66 |
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/component/src/test/java/io/siddhi/extension/io/kafka/multidc/KafkaMultiDCSinkTestCases.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka.multidc;
20 |
21 | import io.siddhi.core.SiddhiAppRuntime;
22 | import io.siddhi.core.SiddhiManager;
23 | import io.siddhi.core.event.Event;
24 | import io.siddhi.core.stream.input.InputHandler;
25 | import io.siddhi.core.stream.output.StreamCallback;
26 | import io.siddhi.core.util.SiddhiTestHelper;
27 | import io.siddhi.extension.io.kafka.KafkaTestUtil;
28 | import org.apache.logging.log4j.LogManager;
29 | import org.apache.logging.log4j.Logger;
30 | import org.junit.Assert;
31 | import org.testng.annotations.AfterClass;
32 | import org.testng.annotations.BeforeClass;
33 | import org.testng.annotations.BeforeMethod;
34 | import org.testng.annotations.Test;
35 |
36 | import java.rmi.RemoteException;
37 | import java.util.concurrent.ExecutorService;
38 | import java.util.concurrent.Executors;
39 | import java.util.concurrent.atomic.AtomicInteger;
40 |
41 | /**
42 | * Class implementing the Test cases for KafkaMultiDCSink.
43 | */
44 | public class KafkaMultiDCSinkTestCases {
45 | private static final Logger LOG = LogManager.getLogger(KafkaMultiDCSinkTestCases.class);
46 | private static ExecutorService executorService;
47 | private AtomicInteger count;
48 |
49 | @BeforeClass
50 | public static void init() throws Exception {
51 | try {
52 | executorService = Executors.newFixedThreadPool(5);
53 | KafkaTestUtil.cleanLogDir();
54 | KafkaTestUtil.setupKafkaBroker();
55 | Thread.sleep(1000);
56 | KafkaTestUtil.cleanLogDir2();
57 | KafkaTestUtil.setupKafkaBroker2();
58 | Thread.sleep(1000);
59 | } catch (Exception e) {
60 | throw new RemoteException("Exception caught when starting server", e);
61 | }
62 | }
63 |
64 | @AfterClass
65 | public static void stopKafkaBroker() throws InterruptedException {
66 | KafkaTestUtil.stopKafkaBroker();
67 | Thread.sleep(1000);
68 | KafkaTestUtil.stopKafkaBroker2();
69 | Thread.sleep(1000);
70 | while (!executorService.isShutdown() || !executorService.isTerminated()) {
71 | executorService.shutdown();
72 | }
73 | }
74 |
75 | @BeforeMethod
76 | public void reset() {
77 | count = new AtomicInteger(0);
78 | }
79 |
80 | @Test
81 | public void testMultiDCSinkWithBothBrokersRunning() throws InterruptedException {
82 | LOG.info("Creating test for publishing events for static topic without a partition");
83 | String[] topics = new String[]{"myTopic"};
84 | KafkaTestUtil.createTopic(KafkaTestUtil.ZK_SERVER_CON_STRING, topics, 1);
85 | KafkaTestUtil.createTopic(KafkaTestUtil.ZK_SERVER2_CON_STRING, topics, 1);
86 | Thread.sleep(4000);
87 |
88 | SiddhiManager sourceOneSiddhiManager = new SiddhiManager();
89 | SiddhiAppRuntime sourceOneApp = sourceOneSiddhiManager.createSiddhiAppRuntime(
90 | "@App:name('SourceOneSiddhiApp') " +
91 | "define stream BarStream2 (symbol string, price float, volume long); " +
92 | "@info(name = 'query1') " +
93 | "@source(type='kafka', topic.list='myTopic', group.id='single_topic_test'," +
94 | "partition.no.list='0', seq.enabled='true'," +
95 | "threading.option='single.thread', bootstrap.servers='localhost:9092'," +
96 | "@map(type='xml'))" +
97 | "Define stream FooStream2 (symbol string, price float, volume long);" +
98 | "from FooStream2 select symbol, price, volume insert into BarStream2;");
99 |
100 | sourceOneApp.addCallback("BarStream2", new StreamCallback() {
101 | @Override
102 | public void receive(Event[] events) {
103 | for (Event event : events) {
104 | LOG.info(event);
105 | count.getAndIncrement();
106 | }
107 | }
108 | });
109 | sourceOneApp.start();
110 | Thread.sleep(4000);
111 |
112 | SiddhiManager sourceTwoSiddhiManager = new SiddhiManager();
113 | SiddhiAppRuntime sourceTwoApp = sourceTwoSiddhiManager.createSiddhiAppRuntime(
114 | "@App:name('SourceTwoSiddhiApp') " +
115 | "define stream BarStream2 (symbol string, price float, volume long); " +
116 | "@info(name = 'query1') " +
117 | "@source(type='kafka', topic.list='myTopic', group.id='single_topic_test'," +
118 | "partition.no.list='0', seq.enabled='true'," +
119 | "threading.option='single.thread', bootstrap.servers='localhost:9093'," +
120 | "@map(type='xml'))" +
121 | "Define stream FooStream2 (symbol string, price float, volume long);" +
122 | "from FooStream2 select symbol, price, volume insert into BarStream2;");
123 |
124 | sourceTwoApp.addCallback("BarStream2", new StreamCallback() {
125 | @Override
126 | public void receive(Event[] events) {
127 | for (Event event : events) {
128 | LOG.info(event);
129 | count.getAndIncrement();
130 | }
131 | }
132 | });
133 | sourceTwoApp.start();
134 | Thread.sleep(4000);
135 |
136 | String sinkApp = "@App:name('SinkSiddhiApp') \n"
137 | + "define stream FooStream (symbol string, price float, volume long); \n"
138 | + "@info(name = 'query1') \n"
139 | + "@sink("
140 | + "type='kafkaMultiDC', "
141 | + "topic='myTopic', "
142 | + "partition='0',"
143 | + "bootstrap.servers='localhost:9092,localhost:9093', "
144 | + "@map(type='xml'))" +
145 | "Define stream BarStream (symbol string, price float, volume long);\n" +
146 | "from FooStream select symbol, price, volume insert into BarStream;\n";
147 |
148 | SiddhiManager siddhiManager = new SiddhiManager();
149 | SiddhiAppRuntime siddhiAppRuntimeSink = siddhiManager.createSiddhiAppRuntime(sinkApp);
150 | InputHandler fooStream = siddhiAppRuntimeSink.getInputHandler("BarStream");
151 | siddhiAppRuntimeSink.start();
152 | Thread.sleep(4000);
153 | fooStream.send(new Object[]{"WSO2", 55.6f, 100L});
154 | fooStream.send(new Object[]{"WSO2", 75.6f, 102L});
155 | fooStream.send(new Object[]{"WSO2", 57.6f, 103L});
156 |
157 | SiddhiTestHelper.waitForEvents(8000, 2, count, 40000);
158 | Assert.assertEquals(6, count.get());
159 | sourceOneApp.shutdown();
160 | sourceTwoApp.shutdown();
161 | siddhiAppRuntimeSink.shutdown();
162 | Thread.sleep(1000);
163 |
164 | }
165 |
166 | /*
167 | Even if one of the brokers are failing publishing should not be stopped for the other broker. Therefore, one
168 | siddhi app must receive events.
169 | */
170 | @Test (dependsOnMethods = "testMultiDCSinkWithBothBrokersRunning")
171 | public void testMultiDCSinkWithOneBrokersFailing() throws InterruptedException {
172 | LOG.info("Creating test for publishing events for static topic without a partition");
173 | String topics[] = new String[]{"myTopic"};
174 | KafkaTestUtil.createTopic(topics, 1);
175 |
176 | // Stopping 2nd Kafka broker to mimic a broker failure
177 | KafkaTestUtil.stopKafkaBroker2();
178 | Thread.sleep(4000);
179 |
180 | SiddhiManager sourceOneSiddhiManager = new SiddhiManager();
181 | SiddhiAppRuntime sourceOneApp = sourceOneSiddhiManager.createSiddhiAppRuntime(
182 | "@App:name('SourceSiddhiApp') " +
183 | "define stream BarStream2 (symbol string, price float, volume long); " +
184 | "@info(name = 'query1') " +
185 | "@source(type='kafka', topic.list='myTopic', group.id='single_topic_test'," +
186 | "partition.no.list='0', seq.enabled='true'," +
187 | "threading.option='single.thread', bootstrap.servers='localhost:9092'," +
188 | "@map(type='xml'))" +
189 | "Define stream FooStream2 (symbol string, price float, volume long);" +
190 | "from FooStream2 select symbol, price, volume insert into BarStream2;");
191 |
192 | sourceOneApp.addCallback("BarStream2", new StreamCallback() {
193 | @Override
194 | public synchronized void receive(Event[] events) {
195 | for (Event event : events) {
196 | LOG.info(event);
197 | count.getAndIncrement();
198 | }
199 | }
200 | });
201 | sourceOneApp.start();
202 | Thread.sleep(4000);
203 |
204 | String sinkApp = "@App:name('SinkSiddhiApp') \n"
205 | + "define stream FooStream (symbol string, price float, volume long); \n"
206 | + "@info(name = 'query1') \n"
207 | + "@sink("
208 | + "type='kafkaMultiDC', "
209 | + "topic='myTopic', "
210 | + "partition='0',"
211 | + "bootstrap.servers='localhost:9092,localhost:9093', "
212 | + "@map(type='xml'))" +
213 | "Define stream BarStream (symbol string, price float, volume long);\n" +
214 | "from FooStream select symbol, price, volume insert into BarStream;\n";
215 |
216 | SiddhiManager siddhiManager = new SiddhiManager();
217 | SiddhiAppRuntime siddhiAppRuntimeSink = siddhiManager.createSiddhiAppRuntime(sinkApp);
218 | InputHandler fooStream = siddhiAppRuntimeSink.getInputHandler("BarStream");
219 | siddhiAppRuntimeSink.start();
220 | Thread.sleep(4000);
221 | fooStream.send(new Object[]{"WSO2", 55.6f, 100L});
222 | fooStream.send(new Object[]{"WSO2", 75.6f, 102L});
223 | fooStream.send(new Object[]{"WSO2", 57.6f, 103L});
224 | Thread.sleep(4000);
225 |
226 | Assert.assertEquals(3, count.get());
227 | sourceOneApp.shutdown();
228 | siddhiAppRuntimeSink.shutdown();
229 |
230 | }
231 |
232 | }
233 |
--------------------------------------------------------------------------------
/component/src/test/java/io/siddhi/extension/io/kafka/multidc/KafkaMultiDCSourceSynchronizerTestCases.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
3 | *
4 | * WSO2 Inc. licenses this file to you under the Apache License,
5 | * Version 2.0 (the "License"); you may not use this file except
6 | * in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing,
12 | * software distributed under the License is distributed on an
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | * KIND, either express or implied. See the License for the
15 | * specific language governing permissions and limitations
16 | * under the License.
17 | */
18 |
19 | package io.siddhi.extension.io.kafka.multidc;
20 |
21 | import io.siddhi.core.stream.input.source.SourceEventListener;
22 | import io.siddhi.extension.io.kafka.multidc.source.SourceSynchronizer;
23 | import io.siddhi.query.api.definition.StreamDefinition;
24 | import org.apache.logging.log4j.LogManager;
25 | import org.apache.logging.log4j.Logger;
26 | import org.junit.Assert;
27 | import org.junit.Test;
28 | import org.testng.annotations.BeforeMethod;
29 |
30 | import java.util.ArrayList;
31 | import java.util.List;
32 |
33 | /**
34 | * Class implementing the Test cases for KafkaMultiDCSource Synchronize Test Case.
35 | */
36 | public class KafkaMultiDCSourceSynchronizerTestCases {
37 | private static final Logger LOG = LogManager.getLogger(KafkaMultiDCSourceSynchronizerTestCases.class);
38 | private static final String SOURCE_1 = "source:9000";
39 | private static final String SOURCE_2 = "source2:9000";
40 | private static String[] servers = {SOURCE_1, SOURCE_2};
41 | private List