├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── pom.xml ├── renovate.json ├── zeebe-cloudevents-common ├── pom.xml └── src │ └── main │ ├── java │ └── io │ │ └── zeebe │ │ └── cloudevents │ │ ├── CloudEventsHelper.java │ │ ├── Headers.java │ │ ├── ZeebeCloudEventBuilder.java │ │ ├── ZeebeCloudEventExtension.java │ │ └── ZeebeCloudEventsHelper.java │ └── resources │ └── application.properties └── zeebe-cloudevents-spring ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── zeebe │ │ └── cloud │ │ └── events │ │ └── router │ │ ├── ActivityJob.java │ │ ├── CloudEventsJobHandler.java │ │ ├── CloudEventsZeebeMappingsService.java │ │ ├── DeployWorkflowPayload.java │ │ ├── MessageForProcessDefinitionKey.java │ │ ├── WorkflowByCloudEvent.java │ │ ├── ZeebeCloudEventsRouterApp.java │ │ ├── ZeebeCloudEventsRouterController.java │ │ ├── ZeebeCloudEventsRouterModes.java │ │ └── ZeebeCloudEventsRouterWorker.java └── resources │ └── application.properties └── test └── java └── io └── zeebe └── cloud └── events └── router └── ZeebeCloudEventsRouterAppTests.java /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build project with Maven 2 | on: 3 | pull_request: 4 | schedule: 5 | - cron: '2 2 * * 1' # run nightly master builds every monday 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Java setup 14 | uses: actions/setup-java@v4 15 | with: 16 | distribution: 'temurin' 17 | java-version: 17 18 | - name: Cache 19 | uses: actions/cache@v4 20 | with: 21 | path: ~/.m2/repository 22 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 23 | restore-keys: | 24 | ${{ runner.os }}-maven- 25 | - name: Run Maven 26 | run: mvn -B clean verify com.mycila:license-maven-plugin:check -Dfmt.skip -Denforcer.skip 27 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy artifacts with Maven 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-24.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Cache 11 | uses: actions/cache@v4 12 | with: 13 | path: ~/.m2/repository 14 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 15 | restore-keys: | 16 | ${{ runner.os }}-maven- 17 | - name: Set up Java environment 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: 17 21 | gpg-private-key: ${{ secrets.MAVEN_CENTRAL_GPG_SIGNING_KEY_SEC }} 22 | gpg-passphrase: MAVEN_CENTRAL_GPG_PASSPHRASE 23 | - name: Login to Docker 24 | run: | 25 | # new login with new container registry url and PAT 26 | echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin 27 | - name: Deploy SNAPSHOT / Release 28 | uses: camunda-community-hub/community-action-maven-release@v1 29 | with: 30 | release-version: ${{ github.event.release.tag_name }} 31 | release-profile: community-action-maven-release 32 | nexus-usr: ${{ secrets.NEXUS_USR }} 33 | nexus-psw: ${{ secrets.NEXUS_PSW }} 34 | maven-usr: ${{ secrets.MAVEN_CENTRAL_DEPLOYMENT_USR }} 35 | maven-psw: ${{ secrets.MAVEN_CENTRAL_DEPLOYMENT_PSW }} 36 | maven-url: oss.sonatype.org 37 | maven-gpg-passphrase: ${{ secrets.MAVEN_CENTRAL_GPG_SIGNING_KEY_PASSPHRASE }} 38 | github-token: ${{ secrets.GITHUB_TOKEN }} 39 | maven-additional-options: -Dfmt.skip -Denforcer.skip 40 | id: release 41 | - if: github.event.release 42 | name: Attach artifacts to GitHub Release (Release only) 43 | uses: actions/upload-release-asset@v1 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | with: 47 | upload_url: ${{ github.event.release.upload_url }} 48 | asset_path: ${{ steps.release.outputs.artifacts_archive_path }} 49 | asset_name: ${{ steps.release.outputs.artifacts_archive_path }} 50 | asset_content_type: application/zip 51 | -------------------------------------------------------------------------------- /.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 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | *.iml 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | .idea/ 25 | target/ 26 | .classpath 27 | .factorypath 28 | .project 29 | .settings/ 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | We welcome your contributions to this project. 4 | 5 | It could be: 6 | 7 | * a [bug report](#bug-reports) in a GitHub issue 8 | * a [feature request](#feature-requests) in a GitHub issue 9 | * a [fix to documentation](#fix-to-documentation) 10 | * a code contribution to [address an existing bug or feature request](#code-contribution-for-an-existing-issue) 11 | * a code contribution to [fix a bug that you found](#code-contribution-for-a-new-bug) 12 | * a code contribution to [add a new feature](#code-contribution-for-a-new-feature) 13 | 14 | By contributing to this project, you license your contribution under the same [license](#license) that we use. 15 | 16 | ## Bug reports 17 | 18 | Before opening a bug report, please search the existing GitHub issues to see if it has already been reported. 19 | 20 | ### Existing bug issues 21 | 22 | If you find an existing bug report, please give the original report a thumbs-up. This helps us to understand how many people are affected by it. 23 | 24 | If you have additional information that can help us understand the behaviour of the bug, how it presents in your configuration, or its impact, please add that as a comment. 25 | 26 | ### New bug reports 27 | 28 | If no issue already exists for the bug, please create a new one. 29 | 30 | When creating a bug report, please fill out the template. What really helps us are the steps for us to be able reproduce the problem in front of us. 31 | 32 | To generate these, start from nothing, and document the steps required to set up a project that shows the bug. If you create such a project as a new GitHub repo, you will have a Minimal Reproducible Example. We can then check out that project and see the bug in front of us immediately. This will increase the speed that we can address the issue. It will also help you isolate the actual issue, and sometimes to fix it. 33 | 34 | ## Feature Requests 35 | 36 | Before opening a feature request, please search the existing GitHub issues to see if it already exists. 37 | 38 | ### Existing feature requests 39 | 40 | If you find an existing feature request, please give the original report a thumbs-up. This helps us to understand how many people want it. 41 | 42 | If you have additional information that can help us understand the behaviour you need, or another use case not covered in the existing comments, please add that as a comment. 43 | 44 | ### New feature requests 45 | 46 | If no issue already exists for the feature request, please create a new one. 47 | 48 | When creating a feature request, please fill out the template. What really helps us are both the motivation for the feature (what is your use-case and what you want to achieve), as well as what you would like the feature to be. Sometimes there is an existing way to accomplish what you want, and we may be able to recommend that. 49 | 50 | If there is no existing way to do it, then understanding the use-case that motivates the feature request helps us to triage it, and also to design the feature implementation. 51 | 52 | ## Fix to documentation 53 | 54 | Maybe while getting started, you notice a step or a dependency that we missed out, or something that would have helped you. In that case, opening a Pull Request with a patch that adds it will help others when they get started. They may never know to thank you for it, but we will! 55 | 56 | Please read the [Code Style Guidelines](#code-style-guidelines) and [Commit Message Guidelines](#commit-message-guidelines) to ensure that your code matches the coding style of the project. This makes it easier for us to merge your contribution. 57 | 58 | ## Code contribution for an existing issue 59 | 60 | Maybe you patch an existing bug, or implement a requested feature for yourself - without waiting for us to get to it. You can contribute that to the codebase as a pull request. This way, you don't end up maintaining a separate fork. 61 | 62 | See the section [Running a development version](#running-a-development-version) for instructions on how to use your fork locally to test your changes. 63 | 64 | Please read the [Code Style Guidelines](#code-style-guidelines) and [Commit Message Guidelines](#commit-message-guidelines) to ensure that your code matches the coding style of the project. This makes it easier for us to merge your contribution. 65 | 66 | If you are implementing a feature, it is a good idea to comment on the issue with your proposed approach. Early feedback and coordination increases the chances that we can merge it. 67 | 68 | ## Code contribution for a new bug 69 | 70 | Maybe you find a bug, dig into the source code, and patch it for yourself. See the section [Running a development version](#running-a-development-version) for instructions on how to use your fork locally to test your changes. 71 | 72 | See the section [Running a development version](#running-a-development-version) for instructions on how to use your fork locally to test your changes. 73 | 74 | Please read the [Code Style Guidelines](#code-style-guidelines) and [Commit Message Guidelines](#commit-message-guidelines) to ensure that your code matches the coding style of the project. This makes it easier for us to merge your contribution. 75 | 76 | When you have a working fork, create a Pull Request, and open a new GitHub issue that describes the issue that you've fixed. If you make the pull request title for your fix "Fixes #${GitHub Issue Number}", then the bug report will be automatically closed when we merge it. 77 | 78 | ## Code contribution for a new feature 79 | 80 | Maybe you implement a missing feature that you want. That's the beauty of open source - we co-create it. To get that merged into the code base, open a feature request issue. It's a good idea to discuss your proposed approach with us in an feature request issue. We might be planning to do it already and have an idea, or maybe we can help you identify the best approach given our familiarity with the codebase. 81 | 82 | See the section [Running a development version](#running-a-development-version) for instructions on how to use your fork locally to test your changes. 83 | 84 | Please read the [Code Style Guidelines](#code-style-guidelines) and [Commit Message Guidelines](#commit-message-guidelines) to ensure that your code matches the coding style of the project. This makes it easier for us to merge your contribution. 85 | 86 | When you have a working fork, create a Pull Request. If you make the pull request title for your fix "Fixes #${GitHub Issue Number}", then the feature request will be automatically closed when we merge it. 87 | 88 | ## Code Style Guidelines 89 | 90 | @TODO: Document the coding style guidelines here. 91 | 92 | Ideally put a [.editorconfig](https://editorconfig.org/) file or similar in the project to automate this, and tell people how to add a plugin to their IDE to utilise it. 93 | 94 | ## Commit Message Guidelines 95 | 96 | @TODO: Document your commit message guidelines here. 97 | 98 | Give specific concrete examples as well as the format. Many people find it easier to work backwards from examples than to work forward from rules. 99 | 100 | ## Running a development version 101 | 102 | @TODO: tell people how to use their fork of your project locally. 103 | 104 | ## License 105 | 106 | This project is licensed under @TODO: license. 107 | 108 | Any contributions you make to this project will be licensed under this license. 109 | 110 | ## Code of Conduct 111 | 112 | This project adheres to the Contributor Covenant [Code of Conduct](https://github.com/zeebe-io/zeebe-client-csharp/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [code-of-conduct@zeebe.io](code-of-conduct@zeebe.io). 113 | -------------------------------------------------------------------------------- /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 | [![Community Extension](https://img.shields.io/badge/Community%20Extension-An%20open%20source%20community%20maintained%20project-FF4700)](https://github.com/camunda-community-hub/community) [![Lifecycle: Incubating](https://img.shields.io/badge/Lifecycle-Incubating-blue)](https://github.com/Camunda-Community-Hub/community/blob/main/extension-lifecycle.md#incubating-) ![Compatible with: Camunda Platform 8](https://img.shields.io/badge/Compatible%20with-Camunda%20Platform%208-0072Ce) 2 | 3 | # Zeebe Knative / CloudEvents Worker 4 | 5 | This project focuses on providing a bridge between [Zeebe](http://zeebe.io/) + [BPMN](https://www.bpmn.org/) to [Knative](https://knative.dev/) and [CloudEvents](https://cloudevents.io/) to provide CloudEvents orchestration using Zeebe Workflows. 6 | 7 | # Service Task Properties 8 | 9 | Properties 10 | - HOST 11 | - TYPE 12 | - MODE: 13 | - EMIT_ONLY: 14 | - WAIT_FOR_CLOUD_EVENT: 15 | - WAIT_TYPE: Cloud Event Type to wait 16 | 17 | The worker has two modes: 18 | - EMIT ONLY: It will emit a CloudEvent and complete the Job 19 | - WAIT FOR CLOUD EVENT: It will wait to receive CloudEvent with a specific Type which will be correlated by the workflow and job key to complete the Service Task. 20 | 21 | # Endpoints 22 | 23 | The worker exposes HTTP Endpoints to receive CloudEvents that can be propagated to workflows. 24 | 25 | - / POST - > Receive CloudEvent via HTTP that will map to a Job 26 | - /message POST -> Receive a CloudEvent that will be forwarded as a BPMN Message for an Intermediate Catch Event 27 | 28 | You can always access the Open API UI here: http://localhost:8080/swagger-ui.html 29 | 30 | # Examples 31 | 32 | ## EMIT and WAIT 33 | 34 | > zbctl deploy emit-wait.bpmn --insecure 35 | > 36 | > zbctl create instance EMIT_WAIT --insecure 37 | > 38 | > curl -X POST localhost:8080/ -H "Content-Type: application/json" -H "Ce-Id: 536808d3" -H "Ce-Type: " -H "Ce-Source: curl" -H "Ce-Subject: ::" -d '{"name":"salaboy"}' -v 39 | > 40 | 41 | ## EMIT and CONTINUE: 42 | > zbctl deploy emit-and-continue.bpmn --insecure 43 | > zbctl create instance EMIT_AND_CONTINUE --variables "{\"myVarId\" : \"123\"}" --insecure 44 | > curl -X POST localhost:8080/message -H "Content-Type: application/json" -H "Ce-Id: 536808d3" -H "Ce-Type: CloudEvent Response" -H "Ce-Source: curl" -H "CorrelationKey: 123" -d '{"name":"salaboy"}' -v 45 | 46 | ## TICKETS 47 | Deploy workflow 48 | > zbctl deploy tickets.bpmn --insecure 49 | 50 | Register Tickets.Purchase event to Start Workflow 51 | > curl -X POST http://localhost:8080/workflows -H "Content-Type: application/json" -d '{"cloudEventType" : "Tickets.Purchase", "processDefinitionKey" : "2251799813690282"}' 52 | 53 | Send Tickets.Purchase to start a workflow 54 | > curl -X POST http://localhost:8080/workflow -H "Content-Type: application/json" -H "Ce-Id: 536808d33" -H "Ce-Type: Tickets.Purchase" -H "Ce-Source: curl" -d '{"sessionId":"5" }' 55 | 56 | # Secrets and Secrets Managers 57 | 58 | This project can be used with `External Secrets` to fetch credentials from external services from cloud provider specific Secret Managers. 59 | In order to install `External Secrets` you can follow the project setup instructions: 60 | 61 | [https://github.com/godaddy/kubernetes-external-secrets](https://github.com/godaddy/kubernetes-external-secrets) 62 | 63 | (touch) 64 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.camunda.community 8 | zeebe-cloudevents-router 9 | v0.1.4 10 | pom 11 | 12 | Zeebe CloudEvents Router 13 | 14 | 15 | https://github.com/camunda-community-hub/zeebe-cloudevents-router 16 | scm:git:git@github.com:camunda-community-hub/zeebe-cloudevents-router.git 17 | scm:git:git@github.com:camunda-community-hub/zeebe-cloudevents-router.git 18 | 19 | HEAD 20 | 21 | 22 | 23 | org.camunda.community 24 | community-hub-release-parent 25 | 1.4.4 26 | 27 | 28 | 29 | 30 | UTF-8 31 | 17 32 | 3.0 33 | 35 | 5.13.0 36 | 3.27.3 37 | 38 | 3.5.3 39 | 3.11.2 40 | 0.8.13 41 | 2.13 42 | 43 | 44 | 3.5.0 45 | 8.5.17 46 | 4.0.1 47 | 48 | 49 | 50 | zeebe-cloudevents-common 51 | zeebe-cloudevents-spring 52 | 53 | 54 | 55 | 56 | 57 | org.camunda.community 58 | zeebe-cloudevents-common 59 | ${project.version} 60 | 61 | 62 | org.camunda.community 63 | zeebe-cloudevents-spring 64 | ${project.version} 65 | 66 | 67 | 68 | 69 | org.junit 70 | junit-bom 71 | ${version.junit} 72 | pom 73 | import 74 | 75 | 76 | org.junit.jupiter 77 | junit-jupiter 78 | ${version.junit} 79 | test 80 | 81 | 82 | org.assertj 83 | assertj-core 84 | ${version.assertj} 85 | test 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-enforcer-plugin 96 | 3.5.0 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | verify 105 | 106 | enforce 107 | 108 | 109 | 110 | 111 | 112 | org.apache.maven.plugins 113 | maven-javadoc-plugin 114 | ${plugin.version.javadoc} 115 | 116 | 117 | 118 | 119 | com.coveo 120 | fmt-maven-plugin 121 | ${plugin.version.fmt} 122 | 123 | 124 | 125 | format 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | org.jacoco 134 | jacoco-maven-plugin 135 | ${plugin.version.jacoco} 136 | 137 | 138 | coverage-initialize 139 | 140 | prepare-agent 141 | 142 | 143 | 144 | coverage-report 145 | post-integration-test 146 | 147 | report 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-surefire-plugin 157 | ${plugin.version.surefire} 158 | 159 | 160 | 161 | 162 | io.zeebe 163 | flaky-test-extractor-maven-plugin 164 | 2.1.1 165 | 166 | 167 | post-integration-test 168 | 169 | extract-flaky-tests 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "lockFileMaintenance": { 7 | "enabled": true, 8 | "automerge": true 9 | }, 10 | "packageRules": [ 11 | { 12 | "automerge": true, 13 | "matchUpdateTypes": ["major", "minor", "patch", "pin", "pinDigest","digest", "lockFileMaintenance", "rollback", "bump", "replacement"], 14 | "automergeType": "branch", 15 | "automergeStrategy": "rebase" 16 | }, 17 | { 18 | "matchPackagePatterns": ["org.camunda.community:zeebe-cloudevents-router"], 19 | "enabled": false 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /zeebe-cloudevents-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.camunda.community 7 | zeebe-cloudevents-router 8 | v0.1.4 9 | 10 | 11 | zeebe-cloudevents-common 12 | 13 | 14 | Zeebe Cloud Events (Common) 15 | 16 | 17 | 1.6.4 18 | 4.12.0 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-webflux 25 | ${spring-boot.version} 26 | 27 | 28 | io.camunda 29 | spring-zeebe-starter 30 | ${zeebe.version} 31 | 32 | 33 | io.cloudevents 34 | cloudevents-core 35 | ${cloudevents.version} 36 | 37 | 38 | io.cloudevents 39 | cloudevents-json-jackson 40 | ${cloudevents.version} 41 | 42 | 43 | 44 | org.projectlombok 45 | lombok 46 | 1.18.38 47 | 48 | 49 | 50 | 51 | org.assertj 52 | assertj-core 53 | test 54 | 55 | 56 | org.junit.jupiter 57 | junit-jupiter 58 | test 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /zeebe-cloudevents-common/src/main/java/io/zeebe/cloudevents/CloudEventsHelper.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloudevents; 2 | 3 | import io.cloudevents.CloudEvent; 4 | 5 | 6 | import io.cloudevents.CloudEventExtension; 7 | import io.cloudevents.core.builder.CloudEventBuilder; 8 | import org.springframework.http.HttpHeaders; 9 | import org.springframework.web.reactive.function.BodyInserters; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | 12 | import java.net.URI; 13 | import java.time.OffsetDateTime; 14 | 15 | 16 | public class CloudEventsHelper { 17 | 18 | public static final String CE_ID = "Ce-Id"; 19 | public static final String CE_TYPE = "Ce-Type"; 20 | public static final String CE_SOURCE = "Ce-Source"; 21 | public static final String CE_SPECVERSION = "Ce-Specversion"; 22 | public static final String CE_TIME = "Ce-Time"; 23 | public static final String CE_SUBJECT = "Ce-Subject"; 24 | 25 | public static final String APPLICATION_JSON = "application/json"; 26 | public static final String CONTENT_TYPE = "Content-Type"; 27 | 28 | 29 | public static CloudEvent parseFromRequest(HttpHeaders headers, Object body) throws IllegalStateException { 30 | 31 | return parseFromRequestWithExtension(headers, body, null); 32 | } 33 | 34 | 35 | public static CloudEvent parseFromRequestWithExtension(HttpHeaders headers, Object body, CloudEventExtension extension){ 36 | if (headers.get(CE_ID) == null || (headers.get(CE_SOURCE) == null || headers.get(CE_TYPE) == null)) { 37 | throw new IllegalStateException("Cloud Event required fields are not present."); 38 | } 39 | 40 | CloudEventBuilder builder = CloudEventBuilder.v1() 41 | .withId(headers.getFirst(CE_ID)) 42 | .withType(headers.getFirst(CE_TYPE)) 43 | .withSource((headers.getFirst(CE_SOURCE) != null) ? URI.create(headers.getFirst(CE_SOURCE)) : null) 44 | .withTime((headers.getFirst(CE_TIME) != null) ? OffsetDateTime.parse(headers.getFirst(CE_TIME)) : null) 45 | .withData(headers.getFirst(CONTENT_TYPE), body.toString().getBytes()) 46 | .withSubject(headers.getFirst(CE_SUBJECT)) 47 | .withDataContentType((headers.getFirst(CONTENT_TYPE) != null) ? headers.getFirst(CONTENT_TYPE) : APPLICATION_JSON); 48 | 49 | if (extension != null) { 50 | builder = builder.withExtension(extension); 51 | } 52 | return builder.build(); 53 | } 54 | 55 | public static WebClient.ResponseSpec createPostCloudEvent(WebClient webClient, CloudEvent cloudEvent) { 56 | return createPostCloudEvent(webClient,"", cloudEvent); 57 | } 58 | 59 | public static WebClient.ResponseSpec createPostCloudEvent(WebClient webClient, String uriString, CloudEvent cloudEvent) { 60 | WebClient.RequestBodySpec uri = webClient.post().uri(uriString); 61 | WebClient.RequestHeadersSpec headersSpec = uri.body(BodyInserters.fromValue(cloudEvent.getData())); 62 | WebClient.RequestHeadersSpec header = headersSpec 63 | .header(CE_ID, cloudEvent.getId()) 64 | .header(CE_SPECVERSION, cloudEvent.getSpecVersion().toString()) 65 | .header(CONTENT_TYPE, APPLICATION_JSON) 66 | .header(CE_TYPE, cloudEvent.getType()) 67 | .header(CE_TIME, (cloudEvent.getTime() != null)?cloudEvent.getTime().toString(): OffsetDateTime.now().toZonedDateTime().toString()) 68 | .header(CE_SOURCE, (cloudEvent.getSource() != null)?cloudEvent.getSource().toString():"") 69 | .header(CE_SUBJECT, cloudEvent.getSubject()); 70 | 71 | 72 | //@TODO: improve extensions handling, at least now we will have a string version of the extension 73 | for (String key : cloudEvent.getExtensionNames()) { 74 | header.header(key, cloudEvent.getExtension(key).toString()); 75 | } 76 | return header.retrieve(); 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /zeebe-cloudevents-common/src/main/java/io/zeebe/cloudevents/Headers.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloudevents; 2 | 3 | public class Headers { 4 | public static final String HOST = "Host"; 5 | public static final String MODE = "Mode"; 6 | public static final String CLOUD_EVENT_WAIT_TYPE = "WaitType"; 7 | public static final String CLOUD_EVENT_TYPE= "Type"; 8 | 9 | public static final String CONTENT_TYPE = "application/cloudevent+json"; 10 | public static final String ZEEBE_CLOUD_EVENTS_EXTENSION = "zeebe"; 11 | } 12 | -------------------------------------------------------------------------------- /zeebe-cloudevents-common/src/main/java/io/zeebe/cloudevents/ZeebeCloudEventBuilder.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloudevents; 2 | 3 | import io.cloudevents.CloudEvent; 4 | import io.cloudevents.core.builder.CloudEventBuilder; 5 | 6 | 7 | public class ZeebeCloudEventBuilder { 8 | private CloudEventBuilder cloudEventBuilder; 9 | private final ZeebeCloudEventExtension zeebeCloudEventExtension; 10 | 11 | public ZeebeCloudEventBuilder(CloudEventBuilder cloudEventBuilder) { 12 | this.cloudEventBuilder = cloudEventBuilder; 13 | zeebeCloudEventExtension = new ZeebeCloudEventExtension(); 14 | } 15 | 16 | public ZeebeCloudEventBuilder withCorrelationKey(String correlationKey) { 17 | zeebeCloudEventExtension.setCorrelationKey(correlationKey); 18 | return this; 19 | } 20 | 21 | public ZeebeCloudEventBuilder withJobKey(String jobKey) { 22 | zeebeCloudEventExtension.setJobKey(jobKey); 23 | return this; 24 | } 25 | 26 | public ZeebeCloudEventBuilder withProcessDefinitionKey(String processDefinitionKey) { 27 | zeebeCloudEventExtension.setProcessDefinitionKey(processDefinitionKey); 28 | return this; 29 | } 30 | 31 | public ZeebeCloudEventBuilder withProcessInstanceKey(String processInstanceKey) { 32 | zeebeCloudEventExtension.setProcessInstanceKey(processInstanceKey); 33 | return this; 34 | } 35 | 36 | public ZeebeCloudEventBuilder withBpmnActivityId(String bpmnActivityId) { 37 | zeebeCloudEventExtension.setBpmnActivityId(bpmnActivityId); 38 | return this; 39 | } 40 | 41 | public ZeebeCloudEventBuilder withBpmnActivityName(String bpmnActivityName) { 42 | zeebeCloudEventExtension.setBpmnActivityName(bpmnActivityName); 43 | return this; 44 | } 45 | 46 | public CloudEvent build() { 47 | return cloudEventBuilder 48 | .withExtension(zeebeCloudEventExtension) 49 | .build(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /zeebe-cloudevents-common/src/main/java/io/zeebe/cloudevents/ZeebeCloudEventExtension.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloudevents; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import io.cloudevents.CloudEventExtensions; 5 | import io.cloudevents.CloudEventExtension; 6 | import io.cloudevents.core.extensions.impl.ExtensionUtils; 7 | 8 | import java.util.*; 9 | 10 | // @TODO: can this live in a library? 11 | @JsonIgnoreProperties(ignoreUnknown = true) 12 | public final class ZeebeCloudEventExtension implements CloudEventExtension { 13 | /** 14 | * The key of the {@code CorrelationKey} extension 15 | */ 16 | public static final String CORRELATION_KEY = "correlationkey"; 17 | public static final String BPMN_ACTIVITY_ID = "bpmnactivityid"; 18 | public static final String BPMN_ACTIVITY_NAME = "bpmnactivityname"; 19 | public static final String PROCESS_DEFINITION_KEY = "processdefinitionkey"; 20 | public static final String PROCESS_INSTANCE_KEY = "processinstancekey"; 21 | public static final String JOB_KEY = "jobkey"; 22 | 23 | private static final Set KEY_SET = Collections 24 | .unmodifiableSet( 25 | new HashSet<>( 26 | Arrays.asList(CORRELATION_KEY, 27 | BPMN_ACTIVITY_ID, 28 | BPMN_ACTIVITY_NAME, 29 | PROCESS_DEFINITION_KEY, 30 | PROCESS_INSTANCE_KEY, 31 | JOB_KEY ))); 32 | 33 | private String correlationKey; 34 | private String bpmnActivityName; 35 | private String bpmnActivityId; 36 | private String processDefinitionKey; 37 | private String processInstanceKey; 38 | private String jobKey; 39 | 40 | public String getCorrelationKey() { 41 | return correlationKey; 42 | } 43 | 44 | public void setCorrelationKey(String correlationKey) { 45 | this.correlationKey = correlationKey; 46 | } 47 | 48 | public String getBpmnActivityName() { 49 | return bpmnActivityName; 50 | } 51 | 52 | public void setBpmnActivityName(String bpmnActivityName) { 53 | this.bpmnActivityName = bpmnActivityName; 54 | } 55 | 56 | public String getBpmnActivityId() { 57 | return bpmnActivityId; 58 | } 59 | 60 | public void setBpmnActivityId(String bpmnActivityId) { 61 | this.bpmnActivityId = bpmnActivityId; 62 | } 63 | 64 | public String getProcessDefinitionKey() { 65 | return processDefinitionKey; 66 | } 67 | 68 | public void setProcessDefinitionKey(String processDefinitionKey) { 69 | this.processDefinitionKey = processDefinitionKey; 70 | } 71 | 72 | public String getProcessInstanceKey() { 73 | return processInstanceKey; 74 | } 75 | 76 | public void setProcessInstanceKey(String processInstanceKey) { 77 | this.processInstanceKey = processInstanceKey; 78 | } 79 | 80 | public String getJobKey() { 81 | return jobKey; 82 | } 83 | 84 | public void setJobKey(String jobKey) { 85 | this.jobKey = jobKey; 86 | } 87 | 88 | // @Override 89 | // public String toString() { 90 | // return Json.encode(this); 91 | // } 92 | 93 | @Override 94 | public boolean equals(Object o) { 95 | if (this == o) return true; 96 | if (o == null || getClass() != o.getClass()) return false; 97 | ZeebeCloudEventExtension that = (ZeebeCloudEventExtension) o; 98 | return Objects.equals(correlationKey, that.correlationKey) && 99 | Objects.equals(bpmnActivityName, that.bpmnActivityName) && 100 | Objects.equals(bpmnActivityId, that.bpmnActivityId) && 101 | Objects.equals(processDefinitionKey, that.processDefinitionKey) && 102 | Objects.equals(processInstanceKey, that.processInstanceKey) && 103 | Objects.equals(jobKey, that.jobKey); 104 | } 105 | 106 | @Override 107 | public int hashCode() { 108 | return Objects.hash(correlationKey, bpmnActivityName, bpmnActivityId, processDefinitionKey, processInstanceKey, jobKey); 109 | } 110 | 111 | @Override 112 | public void readFrom(CloudEventExtensions extensions) { 113 | Object correlationKey = extensions.getExtension(CORRELATION_KEY); 114 | if (correlationKey != null) { 115 | this.correlationKey = correlationKey.toString(); 116 | } 117 | Object bpmnActivityId = extensions.getExtension(BPMN_ACTIVITY_ID); 118 | if (bpmnActivityId != null) { 119 | this.bpmnActivityId = bpmnActivityId.toString(); 120 | } 121 | Object bpmnActivityName = extensions.getExtension(BPMN_ACTIVITY_NAME); 122 | if (bpmnActivityName != null) { 123 | this.bpmnActivityName = bpmnActivityName.toString(); 124 | } 125 | Object processDefinitionKey = extensions.getExtension(PROCESS_DEFINITION_KEY); 126 | if (processDefinitionKey != null) { 127 | this.processDefinitionKey = processDefinitionKey.toString(); 128 | } 129 | Object processInstanceKey = extensions.getExtension(PROCESS_INSTANCE_KEY); 130 | if (processInstanceKey != null) { 131 | this.processInstanceKey = processInstanceKey.toString(); 132 | } 133 | Object jobKey = extensions.getExtension(JOB_KEY); 134 | if (jobKey != null) { 135 | this.jobKey = jobKey.toString(); 136 | } 137 | } 138 | 139 | @Override 140 | public Object getValue(String key) throws IllegalArgumentException { 141 | switch (key) { 142 | case CORRELATION_KEY: 143 | return this.correlationKey; 144 | case BPMN_ACTIVITY_ID: 145 | return this.bpmnActivityId; 146 | case BPMN_ACTIVITY_NAME: 147 | return this.bpmnActivityName; 148 | case PROCESS_DEFINITION_KEY: 149 | return this.processDefinitionKey; 150 | case PROCESS_INSTANCE_KEY: 151 | return this.processInstanceKey; 152 | case JOB_KEY: 153 | return this.jobKey; 154 | } 155 | throw ExtensionUtils.generateInvalidKeyException(this.getClass(), key); 156 | } 157 | 158 | @Override 159 | public Set getKeys() { 160 | return KEY_SET; 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /zeebe-cloudevents-common/src/main/java/io/zeebe/cloudevents/ZeebeCloudEventsHelper.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloudevents; 2 | 3 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | // import io.zeebe.cloudevents.CloudEventsHelper; 7 | import io.cloudevents.CloudEvent; 8 | import io.cloudevents.jackson.JsonCloudEventData; 9 | import io.cloudevents.core.builder.CloudEventBuilder; 10 | import io.cloudevents.core.data.PojoCloudEventData; 11 | import io.camunda.zeebe.client.api.response.ActivatedJob; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.http.HttpHeaders; 15 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 16 | import org.springframework.web.reactive.function.client.WebClient; 17 | import reactor.core.publisher.Mono; 18 | 19 | import java.net.URI; 20 | import java.time.OffsetDateTime; 21 | import java.util.UUID; 22 | 23 | 24 | public class ZeebeCloudEventsHelper { 25 | 26 | private static final Logger log = LoggerFactory.getLogger(ZeebeCloudEventsHelper.class); 27 | 28 | private static ObjectMapper mapper = new ObjectMapper(); 29 | 30 | /* 31 | * This method will parse an HTTP request (headers and body) and it will create a Zeebe Cloud Event, that means 32 | * a Cloud Event From cloudevents.io with a Zeebe Extension 33 | * If the Zeebe Extension is not present in the headers, it will return a base Cloud Event. 34 | */ 35 | public static CloudEvent parseZeebeCloudEventFromRequest(HttpHeaders headers, Object body){ 36 | ZeebeCloudEventExtension zeebeCloudEventExtension = createZeebeCloudEventExtension(headers); 37 | return internalParseCloudEventWithExtensionOrDefault(body, headers, zeebeCloudEventExtension); 38 | } 39 | 40 | private static ZeebeCloudEventExtension createZeebeCloudEventExtension(HttpHeaders headers) { 41 | ZeebeCloudEventExtension zeebeCloudEventExtension = new ZeebeCloudEventExtension(); 42 | zeebeCloudEventExtension.setCorrelationKey(headers.getFirst(ZeebeCloudEventExtension.CORRELATION_KEY)); 43 | zeebeCloudEventExtension.setBpmnActivityId(headers.getFirst(ZeebeCloudEventExtension.BPMN_ACTIVITY_ID)); 44 | zeebeCloudEventExtension.setBpmnActivityName(headers.getFirst(ZeebeCloudEventExtension.BPMN_ACTIVITY_NAME)); 45 | zeebeCloudEventExtension.setProcessDefinitionKey(headers.getFirst(ZeebeCloudEventExtension.PROCESS_DEFINITION_KEY)); 46 | zeebeCloudEventExtension.setProcessInstanceKey(headers.getFirst(ZeebeCloudEventExtension.PROCESS_INSTANCE_KEY)); 47 | zeebeCloudEventExtension.setJobKey(headers.getFirst(ZeebeCloudEventExtension.JOB_KEY)); 48 | return zeebeCloudEventExtension; 49 | } 50 | 51 | private static CloudEvent internalParseCloudEventWithExtensionOrDefault(Object body, HttpHeaders headers, ZeebeCloudEventExtension extension) { 52 | if (extension != null) { 53 | return CloudEventsHelper.parseFromRequestWithExtension(headers, body, extension); 54 | } else { 55 | return CloudEventsHelper.parseFromRequest(headers, body); 56 | } 57 | } 58 | 59 | 60 | 61 | 62 | 63 | 64 | /* 65 | * This method will create a Zeebe Cloud Event from an ActivatedJob inside a worker, this allow other systems to consume 66 | * this Cloud Event and 67 | */ 68 | // public static CloudEvent createZeebeCloudEventFromJob(ActivatedJob job) throws JsonProcessingException { 69 | 70 | 71 | // final ZeebeCloudEventExtension zeebeCloudEventExtension = new ZeebeCloudEventExtension(); 72 | 73 | // // I need to do the HTTP to Cloud Events mapping here, that means picking up the CorrelationKey header and add it to the Cloud Event 74 | // zeebeCloudEventExtension.setBpmnActivityId(String.valueOf(job.getElementInstanceKey())); 75 | // zeebeCloudEventExtension.setBpmnActivityName(job.getElementId()); 76 | // zeebeCloudEventExtension.setJobKey(String.valueOf(job.getKey())); 77 | // zeebeCloudEventExtension.setProcessDefinitionKey(String.valueOf(job.getProcessDefinitionKey())); 78 | // zeebeCloudEventExtension.setProcessInstanceKey(String.valueOf(job.getProcessInstanceKey())); 79 | // ObjectMapper objectMapper = new ObjectMapper(); 80 | // log.info(">>>>> Job Variables: " + objectMapper.writeValueAsString(job.getVariables())); 81 | 82 | // // PojoCloudEventData mappedData = CloudEventUtils.mapData( 83 | 84 | // final CloudEvent zeebeCloudEvent = CloudEventBuilder.v1() 85 | // .withId(UUID.randomUUID().toString()) 86 | // .withTime(OffsetDateTime.now()) // bug-> https://github.com/cloudevents/sdk-java/issues/200 87 | // .withType(job.getCustomHeaders().get(Headers.CLOUD_EVENT_TYPE)) // from headers 88 | // .withSource(URI.create("workflow-zeebe.workflow.svc.cluster.local")) 89 | // .withData(objectMapper.writeValueAsString(job.getVariables()).getBytes()) 90 | // // .withData("application/json", new JsonCloudEventData(JsonNodeFactory.instance.String(job.getVariables()))) 91 | // .withDataContentType(Headers.CONTENT_TYPE) 92 | // .withSubject("Zeebe Job") 93 | // .withExtension(zeebeCloudEventExtension) 94 | // .build(); 95 | 96 | // return zeebeCloudEvent; 97 | // } 98 | 99 | 100 | /* 101 | * Using a CloudEventsBuilder we can create a ZeebeCloudEventBuilder where we can add the Zeebe Extension parameters 102 | * and then build a ZeebeCloudEvent. 103 | */ 104 | // public static ZeebeCloudEventBuilder buildZeebeCloudEvent(CloudEventBuilder cloudEventBuilder){ 105 | // return new ZeebeCloudEventBuilder(cloudEventBuilder); 106 | // } 107 | 108 | // public static void emitZeebeCloudEventHTTPFromJob(ActivatedJob job, String host) throws JsonProcessingException { 109 | 110 | // final CloudEvent myCloudEvent = ZeebeCloudEventsHelper.createZeebeCloudEventFromJob(job); 111 | 112 | // log.info("cloudEvent >>> " + myCloudEvent.toString()); 113 | // WebClient webClient = WebClient.builder().baseUrl(host).filter(logRequest()).build(); 114 | 115 | // // Mono response = webClient.post() 116 | // // .uri("http://localhost:8080/events") 117 | // // .bodyValue(myCloudEvent) 118 | // // .retrieve() 119 | // // .bodyToMono(CloudEvent.class); 120 | 121 | // // W 122 | 123 | // WebClient.ResponseSpec postCloudEvent = CloudEventsHelper.createPostCloudEvent(webClient, myCloudEvent); 124 | 125 | // postCloudEvent.bodyToMono(String.class).doOnError(t -> t.printStackTrace()) 126 | // .doOnSuccess(s -> log.info("Result -> " + s)).subscribe(); 127 | // } 128 | 129 | // //@TODO: refactor to helper class 130 | // public static ExchangeFilterFunction logRequest() { 131 | // return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { 132 | // log.info("Request: " + clientRequest.method() + " - " + clientRequest.url()); 133 | // clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.info(name + "=" + value))); 134 | // log.info(clientRequest.body().toString()); 135 | // return Mono.just(clientRequest); 136 | // }); 137 | // } 138 | 139 | 140 | 141 | } 142 | -------------------------------------------------------------------------------- /zeebe-cloudevents-common/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.camunda.community 8 | zeebe-cloudevents-router 9 | v0.1.4 10 | 11 | 12 | zeebe-cloudevents-router-spring 13 | 14 | 15 | Zeebe CloudEvents Router Spring 16 | Zeebe CloudEvents Router Spring 17 | 18 | 19 | 20 | 1.8.0 21 | 7.3.1 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-actuator 28 | ${spring-boot.version} 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-webflux 33 | ${spring-boot.version} 34 | 35 | 36 | io.camunda 37 | spring-zeebe-starter 38 | ${zeebe.version} 39 | 40 | 41 | org.camunda.community 42 | zeebe-cloudevents-common 43 | 44 | 45 | io.cloudevents 46 | cloudevents-json-jackson 47 | 4.0.1 48 | 49 | 50 | io.cloudevents 51 | cloudevents-http-basic 52 | 4.0.1 53 | 54 | 55 | io.cloudevents 56 | cloudevents-spring 57 | 4.0.1 58 | 59 | 60 | org.springdoc 61 | springdoc-openapi-webflux-ui 62 | ${springdoc-openapi-ui.version} 63 | 64 | 65 | 66 | io.fabric8 67 | knative-client 68 | ${knative-client.version} 69 | 70 | 71 | 72 | org.projectlombok 73 | lombok 74 | 1.18.38 75 | 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-starter-test 81 | ${spring-boot.version} 82 | test 83 | 84 | 85 | 86 | junit 87 | junit 88 | 89 | 90 | 91 | 92 | 93 | org.junit.jupiter 94 | junit-jupiter 95 | test 96 | 97 | 98 | 99 | org.assertj 100 | assertj-core 101 | test 102 | 103 | 104 | 105 | 106 | 107 | 108 | org.springframework.boot 109 | spring-boot-maven-plugin 110 | ${spring-boot.version} 111 | 112 | 113 | 114 | org.apache.maven.plugins 115 | maven-deploy-plugin 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-surefire-plugin 122 | 123 | 124 | 125 | 126 | 127 | com.google.cloud.tools 128 | jib-maven-plugin 129 | 3.4.5 130 | 131 | 132 | package 133 | 134 | dockerBuild 135 | 136 | 137 | 138 | 139 | 140 | ghcr.io/camunda-community-hub/zeebe-cloudevents-router 141 | ${project.version} 142 | 143 | 144 | 145 | 8080 146 | 147 | 148 | 149 | 150 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/ActivityJob.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | import java.util.Map; 4 | 5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | class ActivityJob { 9 | 10 | private Map uris; 11 | private String format; 12 | 13 | public ActivityJob() { 14 | } 15 | 16 | public ActivityJob(Map uris, String format) { 17 | this.uris = uris; 18 | this.format = format; 19 | } 20 | 21 | public Map getUris() { 22 | return this.uris; 23 | } 24 | 25 | public void setUris(Map uris) { 26 | this.uris = uris; 27 | } 28 | 29 | public String getFormat() { 30 | return this.format; 31 | } 32 | 33 | public void setFormat(String format) { 34 | this.format = format; 35 | } 36 | 37 | 38 | @Override 39 | public String toString() { 40 | return "ActivityJob{" + 41 | "uris='" + uris + '\'' + 42 | ", format=" + format + 43 | '}'; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/CloudEventsJobHandler.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | import org.slf4j.LoggerFactory; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | 6 | import java.io.IOException; 7 | import java.util.concurrent.ExecutionException; 8 | 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | 11 | import org.slf4j.Logger; 12 | 13 | import io.camunda.zeebe.client.api.response.ActivatedJob; 14 | import io.camunda.zeebe.client.api.worker.JobClient; 15 | import io.camunda.zeebe.client.api.worker.JobHandler; 16 | import io.netty.handler.timeout.TimeoutException; 17 | 18 | public class CloudEventsJobHandler implements JobHandler{ 19 | 20 | private final Logger logger = LoggerFactory.getLogger(CloudEventsJobHandler.class); 21 | 22 | @Autowired 23 | private ObjectMapper objectMapper; 24 | 25 | @Override 26 | public void handle(JobClient jobClient, ActivatedJob job) throws IOException, InterruptedException, ExecutionException, TimeoutException { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/CloudEventsZeebeMappingsService.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | //@TODO: refine interface.. to many leaked types here 11 | @Service 12 | public class CloudEventsZeebeMappingsService { 13 | // ProcessDefinitionKey, ProcessInstanceKey, Jobs 14 | private Map>> workflowsPendingJobs = new HashMap<>(); 15 | 16 | private Map> messagesByProcessDefinitionKey = new HashMap<>(); 17 | 18 | private Map startWorkflows = new HashMap<>(); 19 | 20 | public CloudEventsZeebeMappingsService() { 21 | 22 | } 23 | 24 | public void addPendingJob(String processDefinitionKey, String processInstanceKey, String jobKey) { 25 | if (workflowsPendingJobs.get(processDefinitionKey) == null) { 26 | workflowsPendingJobs.put(processDefinitionKey, new HashMap<>()); 27 | } 28 | if (!workflowsPendingJobs.get(processDefinitionKey).containsKey(processInstanceKey)) { 29 | workflowsPendingJobs.get(processDefinitionKey).put(processInstanceKey, new HashSet<>()); 30 | } 31 | workflowsPendingJobs.get(processDefinitionKey).get(processInstanceKey).add(jobKey); 32 | } 33 | 34 | public Map> getPendingJobsForProcessDefinitionKey(String processDefinitionKey) { 35 | return workflowsPendingJobs.get(processDefinitionKey); 36 | } 37 | 38 | public Map> getPendingJobsForProcessInstanceKey(String processInstanceKey) { 39 | 40 | for (String processDefinitionKey : workflowsPendingJobs.keySet()) { 41 | if (workflowsPendingJobs.get(processDefinitionKey).containsKey(processInstanceKey)) { 42 | return workflowsPendingJobs.get(processInstanceKey); 43 | } 44 | } 45 | return null; 46 | } 47 | 48 | public Map>> getAllPendingJobs() { 49 | return workflowsPendingJobs; 50 | } 51 | 52 | 53 | public void removePendingJobFromWorkflow(String processDefinitionKey, String processInstanceKey, String jobKey) { 54 | workflowsPendingJobs.get(processDefinitionKey).get(processInstanceKey).remove(jobKey); 55 | } 56 | 57 | public void addMessageForProcessDefinitionKey(String processDefinitionKey, String messageName) { 58 | if (messagesByProcessDefinitionKey.get(String.valueOf(processDefinitionKey)) == null) { 59 | messagesByProcessDefinitionKey.put(String.valueOf(processDefinitionKey), new HashSet<>()); 60 | } 61 | messagesByProcessDefinitionKey.get(processDefinitionKey).add(String.valueOf(messageName)); 62 | } 63 | 64 | public Map> getAllMessages() { 65 | return messagesByProcessDefinitionKey; 66 | } 67 | 68 | public Set getMessagesByProcessDefinitionKey(String processDefinitionKey) { 69 | return messagesByProcessDefinitionKey.get(processDefinitionKey); 70 | } 71 | 72 | public void registerStartWorkflowByCloudEvent(WorkflowByCloudEvent wbce) { 73 | startWorkflows.put(wbce.getCloudEventType(), wbce); 74 | } 75 | 76 | public WorkflowByCloudEvent getStartWorkflowByCloudEvent(String cloudEventType) { 77 | return startWorkflows.get(cloudEventType); 78 | } 79 | 80 | public Map getStartWorkflowByCloudEvents() { 81 | return startWorkflows; 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/DeployWorkflowPayload.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class DeployWorkflowPayload { 7 | private String name; 8 | private String workflowDefinition; 9 | 10 | public DeployWorkflowPayload() { 11 | } 12 | 13 | public DeployWorkflowPayload(String name, String workflowDefinition) { 14 | this.name = name; 15 | this.workflowDefinition = workflowDefinition; 16 | } 17 | 18 | public String getWorkflowDefinition() { 19 | return workflowDefinition; 20 | } 21 | 22 | public void setWorkflowDefinition(String workflowDefinition) { 23 | this.workflowDefinition = workflowDefinition; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "DeployWorkflowPayload{" + 37 | "name='" + name + '\'' + 38 | ", workflowDefinition='" + workflowDefinition + '\'' + 39 | '}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/MessageForProcessDefinitionKey.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class MessageForProcessDefinitionKey { 7 | private String processDefinitionKey; 8 | private String messageName; 9 | 10 | public MessageForProcessDefinitionKey() { 11 | } 12 | 13 | public MessageForProcessDefinitionKey(String processDefinitionKey, String messageName) { 14 | this.processDefinitionKey = processDefinitionKey; 15 | this.messageName = messageName; 16 | } 17 | 18 | public String getProcessDefinitionKey() { 19 | return processDefinitionKey; 20 | } 21 | 22 | public void setProcessDefinitionKey(String processDefinitionKey) { 23 | this.processDefinitionKey = processDefinitionKey; 24 | } 25 | 26 | public String getMessageName() { 27 | return messageName; 28 | } 29 | 30 | public void setMessageName(String messageName) { 31 | this.messageName = messageName; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/WorkflowByCloudEvent.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class WorkflowByCloudEvent { 8 | private String cloudEventType; 9 | private String bpmnProcessId; 10 | private String processDefinitionKey; 11 | private String version; 12 | 13 | public WorkflowByCloudEvent() { 14 | } 15 | 16 | public WorkflowByCloudEvent(String cloudEventType, String bpmnProcessId) { 17 | this.cloudEventType = cloudEventType; 18 | this.bpmnProcessId = bpmnProcessId; 19 | } 20 | 21 | public WorkflowByCloudEvent(String cloudEventType, String bpmnProcessId, String version) { 22 | this.cloudEventType = cloudEventType; 23 | this.bpmnProcessId = bpmnProcessId; 24 | this.version = version; 25 | } 26 | 27 | public String getCloudEventType() { 28 | return cloudEventType; 29 | } 30 | 31 | public void setCloudEventType(String cloudEventType) { 32 | this.cloudEventType = cloudEventType; 33 | } 34 | 35 | public String getBpmnProcessId() { 36 | return bpmnProcessId; 37 | } 38 | 39 | public void setBpmnProcessId(String bpmnProcessId) { 40 | this.bpmnProcessId = bpmnProcessId; 41 | } 42 | 43 | public String getVersion() { 44 | return version; 45 | } 46 | 47 | public void setVersion(String version) { 48 | this.version = version; 49 | } 50 | 51 | public String getProcessDefinitionKey() { 52 | return processDefinitionKey; 53 | } 54 | 55 | public void setProcessDefinitionKey(String processDefinitionKey) { 56 | this.processDefinitionKey = processDefinitionKey; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "WorkflowByCloudEvent{" + 62 | "cloudEventType='" + cloudEventType + '\'' + 63 | ", bpmnProcessId='" + bpmnProcessId + '\'' + 64 | ", processDefinitionKey='" + processDefinitionKey + '\'' + 65 | ", version='" + version + '\'' + 66 | '}'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/ZeebeCloudEventsRouterApp.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | import io.camunda.zeebe.spring.client.EnableZeebeClient; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @EnableZeebeClient 9 | public class ZeebeCloudEventsRouterApp { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ZeebeCloudEventsRouterApp.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/ZeebeCloudEventsRouterController.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | 4 | import io.cloudevents.CloudEvent; 5 | import io.cloudevents.core.builder.CloudEventBuilder; 6 | import io.cloudevents.spring.http.CloudEventHttpUtils; 7 | import io.cloudevents.spring.webflux.CloudEventHttpMessageReader; 8 | import io.cloudevents.spring.webflux.CloudEventHttpMessageWriter; 9 | import reactor.core.publisher.Mono; 10 | 11 | 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.fasterxml.jackson.databind.node.ObjectNode; 14 | import com.fasterxml.jackson.core.JsonProcessingException; 15 | import io.zeebe.cloudevents.CloudEventsHelper; 16 | 17 | 18 | import io.cloudevents.core.format.EventFormat; 19 | import io.cloudevents.core.provider.EventFormatProvider; 20 | import io.cloudevents.jackson.JsonFormat; 21 | 22 | 23 | 24 | 25 | import io.fabric8.knative.client.DefaultKnativeClient; 26 | import io.fabric8.knative.client.KnativeClient; 27 | import io.fabric8.knative.eventing.v1.Trigger; 28 | import io.fabric8.knative.eventing.v1.TriggerBuilder; 29 | import io.fabric8.knative.eventing.v1.TriggerList; 30 | import io.camunda.zeebe.client.api.response.DeploymentEvent; 31 | import io.camunda.zeebe.client.api.worker.JobClient; 32 | import io.zeebe.cloudevents.ZeebeCloudEventExtension; 33 | import io.zeebe.cloudevents.ZeebeCloudEventsHelper; 34 | import io.camunda.zeebe.model.bpmn.Bpmn; 35 | import io.camunda.zeebe.model.bpmn.BpmnModelInstance; 36 | import io.camunda.zeebe.model.bpmn.instance.Definitions; 37 | import io.camunda.zeebe.model.bpmn.instance.Message; 38 | import io.camunda.zeebe.client.ZeebeClient; 39 | import org.slf4j.Logger; 40 | import org.slf4j.LoggerFactory; 41 | import org.springframework.beans.factory.annotation.Autowired; 42 | import org.springframework.http.HttpHeaders; 43 | import org.springframework.web.bind.annotation.RequestBody; 44 | import org.springframework.web.bind.annotation.RequestHeader; 45 | import org.springframework.web.bind.annotation.PostMapping; 46 | import org.springframework.web.bind.annotation.GetMapping; 47 | import org.springframework.web.bind.annotation.RestController; 48 | 49 | import reactor.core.publisher.Mono; 50 | 51 | import java.io.ByteArrayInputStream; 52 | import java.time.LocalDateTime; 53 | import java.time.format.DateTimeFormatter; 54 | import java.util.Collection; 55 | import java.util.Iterator; 56 | import java.util.Map; 57 | import java.util.Set; 58 | import java.util.stream.Collectors; 59 | 60 | import org.springframework.context.annotation.Configuration; 61 | import org.springframework.boot.web.codec.CodecCustomizer; 62 | import org.springframework.http.codec.CodecConfigurer; 63 | 64 | @RestController 65 | public class ZeebeCloudEventsRouterController { 66 | 67 | private static final Logger log = LoggerFactory.getLogger(ZeebeCloudEventsRouterController.class); 68 | 69 | @Autowired 70 | private ZeebeClient zeebeClient; 71 | 72 | @Autowired 73 | private ObjectMapper objectMapper; 74 | 75 | @Autowired 76 | private CloudEventsZeebeMappingsService mappingsService; 77 | 78 | @Autowired 79 | private JobClient jobClient; 80 | 81 | 82 | @GetMapping("/status") 83 | public String getStatus() { 84 | log.info("> Broker Gateway Address: " + zeebeClient.getConfiguration().getGatewayAddress()); 85 | log.info("> Plain Text Connection Enabled: " + zeebeClient.getConfiguration().isPlaintextConnectionEnabled()); 86 | return "{ \"zeebe.broker.gatewayAddress\": " + zeebeClient.getConfiguration().getGatewayAddress() + ", " + 87 | "\"plainTextConnection\":" + zeebeClient.getConfiguration().isPlaintextConnectionEnabled() + "}"; 88 | } 89 | 90 | @GetMapping("/jobs") 91 | public String printPendingJobs() { 92 | Map>> jobs = mappingsService.getAllPendingJobs(); 93 | return jobs.keySet().stream() 94 | .map(key -> key + "=" + jobs.get(key)) 95 | .collect(Collectors.joining(", ", "{", "}")); 96 | } 97 | 98 | @GetMapping("/messages") 99 | public String messages() { 100 | Map> allExpectedBPMNMessages = mappingsService.getAllMessages(); 101 | return allExpectedBPMNMessages.keySet().stream() 102 | .map(key -> key + "=" + allExpectedBPMNMessages.get(key)) 103 | .collect(Collectors.joining(", ", "{", "}")); 104 | } 105 | 106 | @PostMapping("/") 107 | public void completeProcessInstance(@RequestBody ObjectNode root, @RequestHeader HttpHeaders headers) throws JsonProcessingException { 108 | 109 | CloudEvent cloudEvent = CloudEventHttpUtils.fromHttp(headers).build(); 110 | 111 | String body = objectMapper.writeValueAsString(root); 112 | String processDefinitionKey = (String) cloudEvent.getExtension(ZeebeCloudEventExtension.PROCESS_DEFINITION_KEY); 113 | String processInstanceKey = (String) cloudEvent.getExtension(ZeebeCloudEventExtension.PROCESS_INSTANCE_KEY); 114 | String jobKey = (String) cloudEvent.getExtension(ZeebeCloudEventExtension.JOB_KEY); 115 | 116 | logCloudEvent(cloudEvent); 117 | log.debug(body); 118 | 119 | 120 | if (processDefinitionKey == null || processInstanceKey == null || jobKey == null) { 121 | throw new IllegalStateException("Cloud Event missing Zeebe Extension fields, which are required to complete a job"); 122 | } 123 | 124 | Set pendingJobs = mappingsService.getPendingJobsForProcessDefinitionKey(processDefinitionKey).get(processInstanceKey); 125 | if (pendingJobs != null) { 126 | if (!pendingJobs.isEmpty()) { 127 | if (pendingJobs.contains(jobKey)) { 128 | //@TODO: deal with Optionals for Data 129 | jobClient.newCompleteCommand(Long.valueOf(jobKey)).variables(body).send().join(); 130 | mappingsService.removePendingJobFromWorkflow(processDefinitionKey, processInstanceKey, jobKey); 131 | } else { 132 | log.error("Job Key: " + jobKey + " not found"); 133 | throw new IllegalStateException("Job Key: " + jobKey + " not found"); 134 | } 135 | } else { 136 | log.error("This workflow instance key: " + processInstanceKey + " doesn't have any pending jobs"); 137 | throw new IllegalStateException("This workflow instance key: " + processInstanceKey + " doesn't have any pending jobs"); 138 | } 139 | } else { 140 | log.error("Workflow instance key: " + processInstanceKey + " not found"); 141 | throw new IllegalStateException("Workflow instance key: " + processInstanceKey + " not found"); 142 | } 143 | 144 | // @TODO: decide on return types 145 | // return "OK!"; 146 | } 147 | 148 | private void logCloudEvent(CloudEvent cloudEvent) { 149 | EventFormat format = EventFormatProvider 150 | .getInstance() 151 | .resolveFormat(JsonFormat.CONTENT_TYPE); 152 | log.info("CloudEvent: " + new String(format.serialize(cloudEvent))); 153 | } 154 | 155 | 156 | /** 157 | * Map a BPMN Process ID with a CloudEvent type 158 | * @param wbce 159 | */ 160 | @PostMapping("/workflows") 161 | public void addStartWorkflowCloudEventMapping(@RequestBody WorkflowByCloudEvent wbce) { 162 | mappingsService.registerStartWorkflowByCloudEvent(wbce); 163 | } 164 | 165 | /** 166 | * Retuns all mappings between BPMN Process IDs and CloudEvent types 167 | * @return 168 | */ 169 | @GetMapping("/workflows") 170 | public Map getStartWorkflowCloudEventMapping() { 171 | return mappingsService.getStartWorkflowByCloudEvents(); 172 | } 173 | 174 | // @DeleteMapping("/workflow") 175 | // public void cancelWorkflow(@RequestHeader HttpHeaders headers, @RequestBody Map body) { 176 | // CloudEvent cloudEvent = ZeebeCloudEventsHelper.parseZeebeCloudEventFromRequest(headers, body); 177 | // logCloudEvent(cloudEvent); 178 | // String processInstanceKey = (String) cloudEvent.getExtension(ZeebeCloudEventExtension.PROCESS_INSTANCE_KEY); 179 | // if (processInstanceKey != null && !processInstanceKey.equals("")) { 180 | // zeebeClient.newCancelInstanceCommand(Long.valueOf(processInstanceKey)) 181 | // .send().join(); 182 | // log.info("Cancelling Workflow Instance: " + processInstanceKey); 183 | // } else { 184 | // log.error("There is no Workflow Instance Key available in the Cloud Event: " + cloudEvent); 185 | // } 186 | 187 | // } 188 | 189 | 190 | /** 191 | * Very import, the CE-TYPE states the workflow pipeline to start 192 | * 193 | */ 194 | 195 | /* 196 | TODO 197 | A pipeline is per scenario 198 | If the ActivityJob is a set of DataobjectCollections then 199 | we should start a pipeline for each DataobjectCollection 200 | */ 201 | 202 | @PostMapping("/workflow") 203 | public void createProcessInstance(@RequestBody ObjectNode root, @RequestHeader HttpHeaders headers) throws JsonProcessingException { 204 | 205 | String body = objectMapper.writeValueAsString(root); 206 | 207 | CloudEvent attributes = CloudEventHttpUtils.fromHttp(headers).build(); 208 | 209 | logCloudEvent(attributes); 210 | log.info("body : " + body); 211 | 212 | // String body = objectMapper.writeValueAsString(foo); 213 | 214 | WorkflowByCloudEvent workflowByCloudEvent = mappingsService.getStartWorkflowByCloudEvent(attributes.getType()); 215 | 216 | if(workflowByCloudEvent == null) 217 | { 218 | log.error("wrong started event"); 219 | return; 220 | } 221 | 222 | if (workflowByCloudEvent.getBpmnProcessId() != null && !workflowByCloudEvent.getBpmnProcessId().equals("")) { 223 | //@TODO: deal with empty body for variables 224 | if (workflowByCloudEvent.getVersion() == null || workflowByCloudEvent.getVersion().equals("")) { 225 | zeebeClient.newCreateInstanceCommand().bpmnProcessId(workflowByCloudEvent.getBpmnProcessId()) 226 | .latestVersion().variables(body) 227 | .send() 228 | .join(); 229 | } else { 230 | log.info("getBpmnProcessId ok, getVersion ok"); 231 | zeebeClient.newCreateInstanceCommand().bpmnProcessId(workflowByCloudEvent.getBpmnProcessId()) 232 | .version(Integer.valueOf(workflowByCloudEvent.getVersion())) 233 | .variables(body) 234 | .send() 235 | .join(); 236 | } 237 | } else if (workflowByCloudEvent.getProcessDefinitionKey() != null && !workflowByCloudEvent.getProcessDefinitionKey().equals("")) { 238 | zeebeClient.newCreateInstanceCommand().processDefinitionKey(Long.valueOf(workflowByCloudEvent.getProcessDefinitionKey())) 239 | .variables(body) 240 | .send() 241 | .join(); 242 | } else { 243 | log.error("No workflow was started with: " + workflowByCloudEvent.toString()); 244 | } 245 | 246 | 247 | // StringBuilder sb = new StringBuilder("/workflow all jobs: \n"); 248 | // Map>> all_jobs = mappingsService.getAllPendingJobs(); 249 | // for (Map.Entry>> entry : all_jobs.entrySet()) { 250 | // sb.append(entry.getKey() + " > \n"); 251 | // for (Map.Entry> sub_entry : entry.getValue().entrySet()) { 252 | // sb.append("\n " + sub_entry.getKey() + " > " + String.join(",", sub_entry.getValue())); 253 | // } 254 | // } 255 | // log.info(sb.toString()); 256 | } 257 | 258 | // @Configuration 259 | // public static class CloudEventHandlerConfiguration implements CodecCustomizer { 260 | 261 | // @Override 262 | // public void customize(CodecConfigurer configurer) { 263 | // configurer.customCodecs().register(new CloudEventHttpMessageReader()); 264 | // configurer.customCodecs().register(new CloudEventHttpMessageWriter()); 265 | // } 266 | 267 | // } 268 | 269 | 270 | // @PostMapping("/error") 271 | // public void receiveCloudEventForError(@RequestHeader HttpHeaders headers, @RequestBody Object body) { 272 | // CloudEvent cloudEvent = ZeebeCloudEventsHelper.parseZeebeCloudEventFromRequest(headers, body); 273 | // logCloudEvent(cloudEvent); 274 | 275 | // log.info("I should emit a BPMN Error here... "); 276 | // //zeebeClient.newThrowErrorCommand().errorCode() 277 | // } 278 | 279 | // @PostMapping("/messages") 280 | // public void addExpectedMessage(@RequestBody MessageForWorkflowKey messageForWorkflowKey) { 281 | // //@TODO: Next step check and advertise which messages are expected by which workflows 282 | // // This can be scanned on Deploy Workflow, and we can use that to register the workflow as a consumer of events 283 | // mappingsService.addMessageForWorkflowKey(messageForWorkflowKey.getProcessDefinitionKey(), messageForWorkflowKey.getMessageName()); 284 | // } 285 | 286 | @PostMapping("/message") 287 | public String receiveCloudEventForMessage(@RequestHeader HttpHeaders headers, @RequestBody Object body) throws JsonProcessingException { 288 | log.info("Request Headers in the Router: " + headers.toString()); 289 | CloudEvent cloudEvent = ZeebeCloudEventsHelper.parseZeebeCloudEventFromRequest(headers, body); 290 | logCloudEvent(cloudEvent); 291 | 292 | //@TODO: deal with empty type and no correlation key. 293 | String cloudEventType = cloudEvent.getType(); 294 | String correlationKey = (String) cloudEvent.getExtension(ZeebeCloudEventExtension.CORRELATION_KEY); 295 | 296 | 297 | //@TODO: deal with optional for Data, for empty Data 298 | zeebeClient.newPublishMessageCommand() 299 | .messageName(cloudEventType) 300 | .correlationKey(correlationKey) 301 | .variables(cloudEvent.getData().toString()) 302 | .send().join(); 303 | 304 | // @TODO: decide on return types 305 | return "OK!"; 306 | } 307 | } 308 | 309 | 310 | // @PostMapping("/deploy") 311 | // public void deployWorkflow(@RequestBody DeployWorkflowPayload dwp) { 312 | // try (KnativeClient kn = new DefaultKnativeClient()) { 313 | // // Get all Service objects 314 | // TriggerList triggerList = kn.triggers() 315 | // .inNamespace("default") 316 | // .list(); 317 | // // Iterate through list and print names 318 | // for (Trigger trigger : triggerList.getItems()) { 319 | // System.out.println(trigger.getMetadata().getName() + " -> Broker: " + trigger.getSpec().getBroker() + " -> Filter attr type: " + trigger.getSpec().getFilter().getAttributes().get("type") + " -> Subscriber: " + trigger.getSpec().getSubscriber().getUri()); 320 | // } 321 | 322 | 323 | // BpmnModelInstance bpmnModelInstance = Bpmn.readModelFromStream(new ByteArrayInputStream(dwp.getWorkflowDefinition().getBytes())); 324 | // Definitions definitions = bpmnModelInstance.getDefinitions(); 325 | 326 | // Collection messages = definitions.getModelInstance().getModelElementsByType(Message.class); 327 | // for (Message m : messages) { 328 | // log.info("Message: " + m.getName()); 329 | // kn.triggers().createOrReplace( 330 | // new TriggerBuilder() 331 | // .withNewMetadata() 332 | // .withName("router-" + m.getName().replace(".", "-").toLowerCase()) 333 | // .endMetadata() 334 | // .withNewSpec() 335 | // .withBroker("default") 336 | // .withNewFilter() 337 | // .addToAttributes("type", m.getName()) 338 | // .endFilter() 339 | // .withNewSubscriber() 340 | // .withUri("http://zeebe-cloud-events-router.default.svc.cluster.local/message") 341 | // .endSubscriber() 342 | // .endSpec() 343 | // .build()); 344 | // } 345 | 346 | 347 | // DeploymentEvent deploymentEvent = zeebeClient.newDeployCommand().addProcessModel(bpmnModelInstance, dwp.getName()).send().join(); 348 | // log.info("Deployment Event: " + deploymentEvent); 349 | 350 | // } 351 | // } 352 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/ZeebeCloudEventsRouterModes.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | public enum ZeebeCloudEventsRouterModes { 4 | WAIT_FOR_CLOUD_EVENT, 5 | EMIT_ONLY 6 | } 7 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/java/io/zeebe/cloud/events/router/ZeebeCloudEventsRouterWorker.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | 4 | import io.zeebe.cloudevents.ZeebeCloudEventExtension; 5 | import io.zeebe.cloudevents.CloudEventsHelper; 6 | 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import com.fasterxml.jackson.databind.node.ArrayNode; 10 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 11 | import com.fasterxml.jackson.databind.node.ObjectNode; 12 | import com.fasterxml.jackson.core.JsonProcessingException; 13 | import io.camunda.zeebe.client.api.response.ActivatedJob; 14 | import io.camunda.zeebe.client.api.worker.JobClient; 15 | import io.zeebe.cloudevents.Headers; 16 | // import io.zeebe.cloudevents.ZeebeCloudEventsHelper; 17 | import io.camunda.zeebe.spring.client.EnableZeebeClient; 18 | import io.camunda.zeebe.spring.client.annotation.ZeebeWorker; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.stereotype.Service; 23 | import org.springframework.context.annotation.Configuration; 24 | import org.springframework.boot.web.codec.CodecCustomizer; 25 | import org.springframework.http.codec.CodecConfigurer; 26 | import org.springframework.http.HttpStatus; 27 | import org.springframework.http.HttpHeaders; 28 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 29 | import org.springframework.web.reactive.function.client.WebClient; 30 | import reactor.core.publisher.Mono; 31 | 32 | import java.nio.charset.StandardCharsets; 33 | 34 | import io.cloudevents.spring.http.CloudEventHttpUtils; 35 | import io.cloudevents.core.data.PojoCloudEventData; 36 | import io.cloudevents.CloudEvent; 37 | import io.cloudevents.core.builder.CloudEventBuilder; 38 | import io.cloudevents.jackson.JsonCloudEventData; 39 | import io.cloudevents.core.data.BytesCloudEventData; 40 | import io.cloudevents.spring.webflux.CloudEventHttpMessageReader; 41 | import io.cloudevents.spring.webflux.CloudEventHttpMessageWriter; 42 | 43 | import java.time.Instant; 44 | import java.time.LocalDateTime; 45 | import java.net.URI; 46 | import java.time.OffsetDateTime; 47 | import java.time.format.DateTimeFormatter; 48 | import java.util.UUID; 49 | import java.util.Map; 50 | import java.util.Set; 51 | @EnableZeebeClient 52 | @Service 53 | public class ZeebeCloudEventsRouterWorker { 54 | 55 | private static final Logger log = LoggerFactory.getLogger(ZeebeCloudEventsRouterWorker.class); 56 | 57 | @Autowired 58 | private CloudEventsZeebeMappingsService mappingsService; 59 | 60 | @Autowired 61 | private JobClient jobClient; 62 | 63 | @Configuration 64 | public static class CloudEventHandlerConfiguration implements CodecCustomizer { 65 | 66 | @Override 67 | public void customize(CodecConfigurer configurer) { 68 | configurer.customCodecs().register(new CloudEventHttpMessageReader()); 69 | configurer.customCodecs().register(new CloudEventHttpMessageWriter()); 70 | } 71 | 72 | } 73 | 74 | @ZeebeWorker(name = "cloudevents-router", type = "cloudevents", timeout = 60 * 60 * 24 * 1000) 75 | public void cloudEventsRouterWorker(final JobClient client, final ActivatedJob job) throws JsonProcessingException { 76 | logJob(job); 77 | 78 | //from headers 79 | //@TODO: deal with empty headers for HOST and MODE 80 | String host = job.getCustomHeaders().get(Headers.HOST); 81 | String mode = job.getCustomHeaders().get(Headers.MODE); 82 | 83 | if (mode != null && mode.equals(ZeebeCloudEventsRouterModes.WAIT_FOR_CLOUD_EVENT.name())) 84 | { 85 | //@TODO: register here as consumer.. this is dynamic consumer 86 | //mappingsService.registerEventConsumer(); 87 | //waitForCloudEventType = job.getCustomHeaders().get(Headers.CLOUD_EVENT_WAIT_TYPE); 88 | log.info("WAIT FOR CLOUD EVENT"); 89 | mappingsService.addPendingJob(String.valueOf(job.getProcessDefinitionKey()), String.valueOf(job.getProcessInstanceKey()), String.valueOf(job.getKey())); 90 | //@TODO: notify the job client that the job was forwarded to an external system. In Node Client this is something like jobCount--; 91 | // jobClient.newForwardedCommand() 92 | emitZeebeCloudEventHTTPFromJob(job, host); 93 | } 94 | else if (mode == null || mode.equals("") || mode.equals(ZeebeCloudEventsRouterModes.EMIT_ONLY.name())) 95 | { 96 | emitZeebeCloudEventHTTPFromJob(job, host); 97 | jobClient.newCompleteCommand(job.getKey()).send().join(); 98 | 99 | } 100 | 101 | // log.info("Time now: " + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); 102 | 103 | // StringBuilder sb = new StringBuilder("worker all jobs: \n"); 104 | // Map>> all_jobs = mappingsService.getAllPendingJobs(); 105 | // for (Map.Entry>> entry : all_jobs.entrySet()) { 106 | // sb.append(entry.getKey() + " > \n"); 107 | // for (Map.Entry> sub_entry : entry.getValue().entrySet()) { 108 | // sb.append("\n " + sub_entry.getKey() + " > " + String.join(",", sub_entry.getValue())); 109 | // } 110 | // } 111 | // log.info(sb.toString()); 112 | } 113 | 114 | public void emitZeebeCloudEventHTTPFromJob(ActivatedJob job, String host) throws JsonProcessingException { 115 | 116 | final CloudEvent zeebeCloudEvent = createZeebeCloudEventFromJob(job); 117 | 118 | // ZeebeCloudEventExtension zeebeCloudEventExtension = new ZeebeCloudEventExtension(); 119 | 120 | // // I need to do the HTTP to Cloud Events mapping here, that means picking up the CorrelationKey header and add it to the Cloud Event 121 | // zeebeCloudEventExtension.setBpmnActivityId(String.valueOf(job.getElementInstanceKey())); 122 | // zeebeCloudEventExtension.setBpmnActivityName(job.getElementId()); 123 | // zeebeCloudEventExtension.setJobKey(String.valueOf(job.getKey())); 124 | // zeebeCloudEventExtension.setProcessDefinitionKey(String.valueOf(job.getProcessDefinitionKey())); 125 | // zeebeCloudEventExtension.setProcessInstanceKey(String.valueOf(job.getProcessInstanceKey())); 126 | 127 | 128 | //TODO: 129 | // loop counter: search a data_objects field 130 | 131 | // construct a JSONNode 132 | // search for data_objects node 133 | // if true -> take it and send it to the client 134 | // if false -> send directly to the client 135 | 136 | 137 | ObjectMapper objectMapper = new ObjectMapper(); 138 | 139 | log.info(">>>>> Job Variables: " + objectMapper.writeValueAsString(job.getVariables())); 140 | 141 | JsonNode root = objectMapper.readTree(job.getVariables()); 142 | 143 | ArrayNode arrNode = (ArrayNode) root.get("data_objects_collection"); 144 | 145 | String body; 146 | 147 | if ((arrNode!=null) && arrNode.isArray()) { 148 | log.info("IS AN ARRAY"); 149 | int counter = root.get("loopCounter").asInt(); 150 | 151 | JsonNode elt = arrNode.get(counter-1); 152 | body = objectMapper.writeValueAsString(elt); 153 | } 154 | else 155 | { 156 | log.info("IS NOT AN ARRAY"); 157 | JsonNode t = root.get("data_objects"); 158 | 159 | if(t == null) 160 | { 161 | log.info("field name is null "); 162 | return; 163 | } 164 | 165 | body = objectMapper.writeValueAsString(t); 166 | 167 | } 168 | 169 | log.info(">>>>> Activity: " + body); 170 | 171 | // ActivityJob activity = objectMapper.readValue(job.getVariables(), ActivityJob.class); 172 | // log.info(">>>>> Activity: " + body); 173 | 174 | // String tmp = objectMapper.writeValueAsString(activity); 175 | // log.info(">>>>> tmp: " + tmp.toString()); 176 | 177 | // CloudEvent zeebeCloudEvent = CloudEventBuilder.v1() 178 | // .withId(UUID.randomUUID().toString()) 179 | // .withTime(OffsetDateTime.now()) // bug-> https://github.com/cloudevents/sdk-java/issues/200 180 | // .withType(job.getCustomHeaders().get(Headers.CLOUD_EVENT_TYPE)) // from headers 181 | // .withSource(URI.create("http://workflow-zeebe.workflow.svc.cluster.local")) 182 | // // .withData(job.getVariables().getBytes()) 183 | // // .withData("application/json", JsonCloudEventData.wrap(variables)) 184 | // // .withData(objectMapper.writeValueAsBytes(activity)) // job.getVariables().getBytes()) 185 | // // .withData(objectMapper.writeValueAsString(activity).getBytes())//PojoCloudEventData.wrap(activity, objectMapper::writeValueAsBytes)) 186 | // // .withData("{\"uri\":\"Dave\"}".getBytes()) 187 | // // .withData(objectMapper.writeValueAsString(activity).getBytes(StandardCharsets.UTF_8)) 188 | // // .withDataContentType(Headers.CONTENT_TYPE) 189 | // .withDataContentType("application/json; charset=UTF-8") 190 | // .withSubject("Zeebe Job") 191 | // .withExtension(zeebeCloudEventExtension) 192 | // .build(); 193 | 194 | 195 | log.info("cloudEvent >>> " + zeebeCloudEvent.toString()); 196 | // WebClient webClient = WebClient.builder().baseUrl(host) 197 | // .filters(exchangeFilterFunctions -> { 198 | // exchangeFilterFunctions.add(logRequest()); 199 | // // exchangeFilterFunctions.add(logResponse()); 200 | // }).build(); 201 | 202 | 203 | HttpHeaders outgoing = CloudEventHttpUtils.toHttp(zeebeCloudEvent); 204 | 205 | // StringBuilder sb = new StringBuilder("Request: \n"); 206 | // //append clientRequest method and url 207 | // log.info("Request: " + "POST - " + host); 208 | // outgoing.forEach((name, values) -> values.forEach(value -> sb.append(name + "=" + value + " "))); 209 | 210 | // log.info(sb.toString()); 211 | 212 | WebClient.builder() 213 | .baseUrl(host) 214 | .filters(exchangeFilterFunctions -> { 215 | exchangeFilterFunctions.add(logRequest()); 216 | // exchangeFilterFunctions.add(logResponse()); 217 | }) 218 | .build() // ProcessInstanceKey 219 | .post() 220 | .headers(httpHeaders -> httpHeaders.putAll(outgoing)) 221 | .bodyValue(body).retrieve().bodyToMono(String.class).doOnError(t -> t.printStackTrace()) 222 | .doOnSuccess(s -> log.info("Result -> " + s)).subscribe(); 223 | // .post() // 224 | // .bodyValue(zeebeCloudEvent) // 225 | // .retrieve() 226 | // .bodyToMono(String.class) 227 | // .doOnError(t -> t.printStackTrace()) 228 | // .doOnSuccess(s -> log.info("Result -> " + s)).subscribe(); 229 | 230 | 231 | // .exchangeToMono(response -> { 232 | // if (response.statusCode().equals(HttpStatus.OK)) { 233 | // return response.bodyToMono(CloudEvent.class); 234 | // } 235 | // else { 236 | // return response.createException().flatMap(Mono::error); 237 | // } 238 | // }); 239 | 240 | 241 | // (response -> response.bodyToMono(CloudEvent.class)); 242 | 243 | // WebClient.RequestBodySpec uri = webClient.post().uri(uriString); 244 | // WebClient.RequestHeadersSpec headersSpec = uri.body(BodyInserters.fromValue(cloudEvent.getData())); 245 | // WebClient.RequestHeadersSpec header = headersSpec 246 | // .header(CE_ID, cloudEvent.getId()) 247 | // .header(CE_SPECVERSION, cloudEvent.getSpecVersion().toString()) 248 | // .header(CONTENT_TYPE, APPLICATION_JSON) 249 | // .header(CE_TYPE, cloudEvent.getType()) 250 | // .header(CE_TIME, (cloudEvent.getTime() != null)?cloudEvent.getTime().toString(): OffsetDateTime.now().toZonedDateTime().toString()) 251 | // .header(CE_SOURCE, (cloudEvent.getSource() != null)?cloudEvent.getSource().toString():"") 252 | // .header(CE_SUBJECT, cloudEvent.getSubject()); 253 | 254 | 255 | //@TODO: improve extensions handling, at least now we will have a string version of the extension 256 | // for (String key : cloudEvent.getExtensionNames()) { 257 | // header.header(key, cloudEvent.getExtension(key).toString()); 258 | // } 259 | // return header.retrieve(); 260 | 261 | // WebClient.ResponseSpec postCloudEvent = CloudEventsHelper.createPostCloudEvent(webClient, myCloudEvent); 262 | 263 | // log.info("WebClient.ResponseSpec: " + postCloudEvent.toString()); 264 | 265 | // postCloudEvent.bodyToMono(String.class).doOnError(t -> t.printStackTrace()) 266 | // .doOnSuccess(s -> log.info("Result -> " + s)).subscribe(); 267 | } 268 | 269 | /* 270 | * This method will create a Zeebe Cloud Event from an ActivatedJob inside a worker, this allow other systems to consume 271 | * this Cloud Event and 272 | */ 273 | public CloudEvent createZeebeCloudEventFromJob(ActivatedJob job) throws JsonProcessingException { 274 | 275 | 276 | final ZeebeCloudEventExtension zeebeCloudEventExtension = new ZeebeCloudEventExtension(); 277 | 278 | // I need to do the HTTP to Cloud Events mapping here, that means picking up the CorrelationKey header and add it to the Cloud Event 279 | zeebeCloudEventExtension.setBpmnActivityId(String.valueOf(job.getElementInstanceKey())); 280 | zeebeCloudEventExtension.setBpmnActivityName(job.getElementId()); 281 | zeebeCloudEventExtension.setJobKey(String.valueOf(job.getKey())); 282 | zeebeCloudEventExtension.setProcessDefinitionKey(String.valueOf(job.getProcessDefinitionKey())); 283 | zeebeCloudEventExtension.setProcessInstanceKey(String.valueOf(job.getProcessInstanceKey())); 284 | 285 | 286 | // ObjectMapper objectMapper = new ObjectMapper(); 287 | 288 | // JsonNode root = objectMapper.readTree(job.getVariables()); 289 | 290 | // JsonNode data_objects = root.get("data_objects_collection"); 291 | 292 | // String body = objectMapper.writeValueAsString(data_objects); 293 | 294 | // log.info(">>>>> Job Variables: " + objectMapper.writeValueAsString(job.getVariables())); 295 | // log.info(">>>>> Activity: " + body); 296 | 297 | // ObjectMapper objectMapper = new ObjectMapper(); 298 | // log.info(">>>>> Job Variables: " + objectMapper.writeValueAsString(job.getVariables())); 299 | 300 | 301 | // ActivityJob activity = objectMapper.readValue(job.getVariables(), ActivityJob.class); 302 | // log.info(">>>>> Activity: " + activity.toString()); 303 | 304 | 305 | // String tmp = objectMapper.writeValueAsString(activity); 306 | // log.info(">>>>> tmp: " + tmp.toString()); 307 | 308 | final CloudEvent zeebeCloudEvent = CloudEventBuilder.v1() 309 | .withId(UUID.randomUUID().toString()) 310 | .withTime(OffsetDateTime.now()) // bug-> https://github.com/cloudevents/sdk-java/issues/200 311 | .withType(job.getCustomHeaders().get(Headers.CLOUD_EVENT_TYPE)) // from headers 312 | .withSource(URI.create("workflow-zeebe.workflow.svc.cluster.local")) 313 | // .withData(job.getVariables().getBytes()) 314 | // .withData("application/json", JsonCloudEventData.wrap(variables)) 315 | // .withData(objectMapper.writeValueAsBytes(activity)) // job.getVariables().getBytes()) 316 | // .withData(objectMapper.writeValueAsString(activity).getBytes())//PojoCloudEventData.wrap(activity, objectMapper::writeValueAsBytes)) 317 | //.withDataContentType(Headers.CONTENT_TYPE) 318 | // .withData(tmp.getBytes()) 319 | .withDataContentType("application/json; charset=UTF-8") 320 | .withSubject("Zeebe Job") 321 | .withExtension(zeebeCloudEventExtension) 322 | .build(); 323 | 324 | return zeebeCloudEvent; 325 | } 326 | 327 | public static ExchangeFilterFunction logRequest() { 328 | return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { 329 | // if (log.isDebugEnabled()) { 330 | StringBuilder sb = new StringBuilder("Request: \n"); 331 | //append clientRequest method and url 332 | log.info("Request: " + clientRequest.method() + " - " + clientRequest.url()); 333 | clientRequest 334 | .headers() 335 | .forEach((name, values) -> values.forEach(value -> sb.append(name + "=" + value + " "))); 336 | log.info(sb.toString()); 337 | // } 338 | return Mono.just(clientRequest); 339 | }); 340 | } 341 | 342 | 343 | private static void logJob(final ActivatedJob job) { 344 | log.info( 345 | "complete job\n>>> [type: {}, key: {}, element: {}, process instance: {}]\n{deadline; {}]\n[headers: {}]\n[variables: {}]", 346 | job.getType(), 347 | job.getKey(), 348 | job.getElementId(), 349 | job.getProcessInstanceKey(), 350 | Instant.ofEpochMilli(job.getDeadline()), 351 | job.getCustomHeaders(), 352 | job.getVariables()); 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #zeebe.client.broker.contactPoint=127.0.0.1:26500 2 | #zeebe.client.security.plaintext=true -------------------------------------------------------------------------------- /zeebe-cloudevents-spring/src/test/java/io/zeebe/cloud/events/router/ZeebeCloudEventsRouterAppTests.java: -------------------------------------------------------------------------------- 1 | package io.zeebe.cloud.events.router; 2 | 3 | // import org.junit.jupiter.api.Test; 4 | // // import org.junit.runner.RunWith; 5 | // import org.springframework.boot.test.context.SpringBootTest; 6 | // // import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | // // @RunWith(SpringRunner.class) 9 | // @SpringBootTest 10 | // public class ZeebeCloudEventsRouterAppTests { 11 | 12 | // @Test 13 | // public void contextLoads() { 14 | // } 15 | 16 | // } 17 | --------------------------------------------------------------------------------