├── .github ├── CODEOWNERS └── workflows │ ├── build-with-bal-test-native.yml │ ├── ci.yml │ ├── daily-build.yml │ ├── dev-stg-release.yml │ ├── pull-request.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── ballerina ├── Ballerina.toml ├── Module.md ├── Package.md ├── client.bal ├── constants.bal ├── data_mappings.bal ├── error.bal ├── external.bal ├── icon.png ├── tests │ └── test.bal ├── types.bal └── utils.bal ├── issue_template.md └── pull_request_template.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # See: https://help.github.com/articles/about-codeowners/ 5 | 6 | # These owners will be the default owners for everything in the repo. 7 | * @NipunaRanasinghe @shafreenAnfar 8 | -------------------------------------------------------------------------------- /.github/workflows/build-with-bal-test-native.yml: -------------------------------------------------------------------------------- 1 | name: GraalVM Check 2 | 3 | on: 4 | schedule: 5 | - cron: '30 18 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up GraalVM 16 | uses: graalvm/setup-graalvm@v1 17 | with: 18 | java-version: '17' 19 | distribution: 'graalvm-community' 20 | set-java-home: false 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: Check GraalVM installation 24 | run: | 25 | echo "GRAALVM_HOME: ${{ env.GRAALVM_HOME }}" 26 | echo "JAVA_HOME: ${{ env.JAVA_HOME }}" 27 | native-image --version 28 | 29 | - name: Set Up Ballerina 30 | uses: ballerina-platform/setup-ballerina@v1.1.0 31 | with: 32 | version: latest 33 | 34 | - name: Run Ballerina tests using the native executable 35 | working-directory: ./ballerina 36 | run: bal test --graalvm 37 | env: 38 | JAVA_HOME: /usr/lib/jvm/default-jvm 39 | ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }} 40 | SECRET_ACCESS_KEY: ${{ secrets.SECRET_ACCESS_KEY }} 41 | REGION: ${{ secrets.REGION }} 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 2201.[0-9]+.x 8 | repository_dispatch: 9 | types: 10 | check_connector_for_breaking_changes 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | # Setup Ballerina Environment 19 | - name: Set Up Ballerina 20 | uses: ballerina-platform/setup-ballerina@v1.1.0 21 | with: 22 | version: latest 23 | 24 | # Build Ballerina Project 25 | - name: Ballerina Build 26 | run: bal pack ./ballerina 27 | env: 28 | JAVA_HOME: /usr/lib/jvm/default-jvm 29 | 30 | # Test Ballerina Project 31 | - name: Ballerina Test 32 | run: bal test ./ballerina --test-report --code-coverage --coverage-format=xml 33 | env: 34 | JAVA_HOME: /usr/lib/jvm/default-jvm 35 | ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }} 36 | SECRET_ACCESS_KEY: ${{ secrets.SECRET_ACCESS_KEY }} 37 | REGION: ${{ secrets.REGION }} 38 | 39 | - name: Upload coverage reports to Codecov 40 | uses: codecov/codecov-action@v3 41 | 42 | # Send notification when build fails 43 | - name: Alert notifier on failure 44 | if: failure() && (github.event.action == 'check_connector_for_breaking_changes') 45 | run: | 46 | curl -X POST \ 47 | 'https://api.github.com/repos/ballerina-platform/ballerina-release/dispatches' \ 48 | --header 'Accept: application/vnd.github.v3+json' \ 49 | --header 'Authorization: Bearer ${{ secrets.BALLERINA_BOT_TOKEN }}' \ 50 | --data-raw '{ 51 | "event_type": "notify-ballerinax-connector-build-failure", 52 | "client_payload": { 53 | "repoName": "module-ballerinax-aws.simpledb", 54 | "workflow": "CI" 55 | } 56 | }' 57 | -------------------------------------------------------------------------------- /.github/workflows/daily-build.yml: -------------------------------------------------------------------------------- 1 | name: Daily build 2 | 3 | on: 4 | schedule: 5 | - cron: '30 2 * * *' 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | # Setup Ballerina Environment 15 | - name: Set Up Ballerina 16 | uses: ballerina-platform/setup-ballerina@v1.1.0 17 | with: 18 | version: latest 19 | 20 | # Build Ballerina Project 21 | - name: Ballerina Build 22 | run: bal pack ./ballerina 23 | env: 24 | JAVA_HOME: /usr/lib/jvm/default-jvm 25 | 26 | # Test Ballerina Project 27 | - name: Ballerina Test 28 | run: bal test ./ballerina --test-report --code-coverage --coverage-format=xml 29 | env: 30 | JAVA_HOME: /usr/lib/jvm/default-jvm 31 | ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }} 32 | SECRET_ACCESS_KEY: ${{ secrets.SECRET_ACCESS_KEY }} 33 | REGION: ${{ secrets.REGION }} 34 | 35 | - name: Upload coverage reports to Codecov 36 | uses: codecov/codecov-action@v3 37 | 38 | # Send notification when build fails 39 | - name: Notify failure 40 | if: ${{ failure() }} 41 | run: | 42 | curl -X POST \ 43 | 'https://api.github.com/repos/ballerina-platform/ballerina-release/dispatches' \ 44 | -H 'Accept: application/vnd.github.v3+json' \ 45 | -H 'Authorization: Bearer ${{ secrets.BALLERINA_BOT_TOKEN }}' \ 46 | --data "{ 47 | \"event_type\": \"notify-build-failure\", 48 | \"client_payload\": { 49 | \"repoName\": \"module-ballerinax-aws.simpledb\" 50 | } 51 | }" 52 | -------------------------------------------------------------------------------- /.github/workflows/dev-stg-release.yml: -------------------------------------------------------------------------------- 1 | name: Dev/Staging BCentral Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | bal_central_environment: 7 | description: Ballerina Central Environment 8 | type: choice 9 | options: 10 | - STAGE 11 | - DEV 12 | required: true 13 | 14 | jobs: 15 | release: 16 | runs-on: ubuntu-latest 17 | env: 18 | BALLERINA_${{ github.event.inputs.bal_central_environment }}_CENTRAL: true 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | # Setup Ballerina Environment 23 | - name: Set Up Ballerina 24 | uses: ballerina-platform/setup-ballerina@v1.1.0 25 | with: 26 | version: 2201.8.0 27 | 28 | # Build Ballerina Project 29 | - name: Ballerina Build 30 | run: bal pack ./ballerina 31 | env: 32 | JAVA_HOME: /usr/lib/jvm/default-jvm 33 | 34 | # Push to Ballerina Staging Central 35 | - name: Push to Staging 36 | if: github.event.inputs.bal_central_environment == 'STAGE' 37 | run: bal push 38 | working-directory: ./ballerina 39 | env: 40 | BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_STAGE_ACCESS_TOKEN }} 41 | JAVA_HOME: /usr/lib/jvm/default-jvm 42 | 43 | # Push to Ballerina Dev Central 44 | - name: Push to Dev 45 | if: github.event.inputs.bal_central_environment == 'DEV' 46 | run: bal push 47 | working-directory: ./ballerina 48 | env: 49 | BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_DEV_ACCESS_TOKEN }} 50 | JAVA_HOME: /usr/lib/jvm/default-jvm 51 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: [ pull_request ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | # Setup Ballerina Environment 12 | - name: Set Up Ballerina 13 | uses: ballerina-platform/setup-ballerina@v1.1.0 14 | with: 15 | version: latest 16 | 17 | # Build Ballerina Project 18 | - name: Ballerina Build 19 | run: bal pack ./ballerina 20 | env: 21 | JAVA_HOME: /usr/lib/jvm/default-jvm 22 | 23 | # Test Ballerina Project 24 | - name: Ballerina Test 25 | # tests will be skipped if the PR is from a forked repository (as the secrets are not available) 26 | if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} 27 | run: bal test ./ballerina --test-report --code-coverage --coverage-format=xml 28 | env: 29 | JAVA_HOME: /usr/lib/jvm/default-jvm 30 | ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }} 31 | SECRET_ACCESS_KEY: ${{ secrets.SECRET_ACCESS_KEY }} 32 | REGION: ${{ secrets.REGION }} 33 | 34 | - name: Upload coverage reports to Codecov 35 | uses: codecov/codecov-action@v3 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Deployment 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | # Setup Ballerina Environment 14 | - name: Set Up Ballerina 15 | uses: ballerina-platform/setup-ballerina@v1.1.0 16 | with: 17 | version: 2201.8.0 18 | 19 | # Build Ballerina Project 20 | - name: Ballerina Build 21 | run: bal pack ./ballerina 22 | env: 23 | JAVA_HOME: /usr/lib/jvm/default-jvm 24 | 25 | # Push to Ballerina Central 26 | - name: Ballerina Push 27 | run: bal push 28 | working-directory: ./ballerina 29 | env: 30 | BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_ACCESS_TOKEN }} 31 | JAVA_HOME: /usr/lib/jvm/default-jvm 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | *.balx 4 | 5 | # Log file 6 | *.log 7 | 8 | #conf file 9 | ballerina.conf 10 | 11 | #lock file 12 | Ballerina.lock 13 | 14 | # BlueJ files 15 | *.ctxt 16 | 17 | # Mobile Tools for Java (J2ME) 18 | .mtj.tmp/ 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | 32 | # Ignore everything in this directory 33 | target 34 | .classpath 35 | .settings 36 | .project 37 | *.iml 38 | *.iws 39 | *.ipr 40 | .idea 41 | .m2 42 | .vscode/ 43 | *.ballerina/ 44 | 45 | # Ignore ballerina files 46 | temp.bal.ballerina/ 47 | target/ 48 | .DS_Store 49 | Config.toml 50 | resources/Client_functions.json 51 | modules/simpledb/resources 52 | 53 | # Ballerina configuartion file 54 | Config.toml 55 | 56 | # Target folder of ballerina project 57 | /target 58 | 59 | # json files created inside resources folder 60 | /modules 61 | 62 | # resource folder in tests folder 63 | /tests/resources 64 | 65 | # resources folder 66 | /resources/*.json 67 | 68 | # idea files 69 | *.idea 70 | 71 | # .ballerina files 72 | *.ballerina 73 | -------------------------------------------------------------------------------- /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 | # Ballerina Amazon SimpleDB Connector 2 | 3 | [![Build](https://github.com/ballerina-platform/module-ballerinax-aws.simpledb/workflows/CI/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-aws.simpledb/actions?query=workflow%3ACI) 4 | [![codecov](https://codecov.io/gh/ballerina-platform/module-ballerinax-aws.simpledb/branch/main/graph/badge.svg)](https://codecov.io/gh/ballerina-platform/module-ballerinax-aws.simpledb) 5 | [![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerinax-aws.simpledb.svg)](https://github.com/ballerina-platform/module-ballerinax-aws.simpledb/commits/master) 6 | [![GraalVM Check](https://github.com/ballerina-platform/module-ballerinax-aws.simpledb/actions/workflows/build-with-bal-test-native.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-aws.simpledb/actions/workflows/build-with-bal-test-native.yml) 7 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 8 | 9 | [Amazon SimpleDB](https://aws.amazon.com/simpledb/) is a simple database service developed by Amazon. 10 | 11 | This connector provides operations for connecting and interacting with Amazon SimpleDB endpoints over the network. Its main capabilities are to create and manage SimpleDB domains. Following modules are available in the connector. 12 | 13 | - [`aws.simpledb`](simpledb/Module.md) 14 | 15 | ## Building from the source 16 | ### Setting up the prerequisites 17 | 18 | 1. Download and install Java SE Development Kit (JDK) version 11. You can install either [OpenJDK](https://adoptopenjdk.net/) or [Oracle JDK](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html). 19 | 20 | > **Note:** Set the JAVA_HOME environment variable to the path name of the directory in which you installed JDK. 21 | 22 | 2. Download and install [Ballerina Swan Lake](https://ballerina.io/). 23 | 24 | ### Building the source 25 | 26 | Execute the commands below to build from the source: 27 | * To build the package: 28 | ``` 29 | bal build ./ballerina 30 | ``` 31 | * To run tests after build: 32 | ``` 33 | bal test ./ballerina 34 | ``` 35 | ## Contributing to Ballerina 36 | As an open source project, Ballerina welcomes contributions from the community. 37 | 38 | For more information, see the [Contribution Guidelines](https://github.com/ballerina-platform/ballerina-lang/blob/master/CONTRIBUTING.md). 39 | 40 | ## Code of conduct 41 | All contributors are encouraged to read the [Ballerina Code of Conduct](https://ballerina.io/code-of-conduct). 42 | 43 | ## Useful links 44 | * Discuss about code changes of the Ballerina project via [ballerina-dev@googlegroups.com](mailto:ballerina-dev@googlegroups.com). 45 | * Chat live with us via our [Discord server](https://discord.gg/ballerinalang). 46 | * Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. 47 | -------------------------------------------------------------------------------- /ballerina/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | distribution = "2201.8.0" 3 | org = "ballerinax" 4 | name = "aws.simpledb" 5 | version = "2.2.0" 6 | license= ["Apache-2.0"] 7 | authors = ["Ballerina"] 8 | keywords= ["IT Operations/Databases", "Cost/Freemium", "Vendor/Amazon"] 9 | repository = "https://github.com/ballerina-platform/module-ballerinax-aws.simpledb" 10 | icon = "icon.png" 11 | 12 | [build-options] 13 | observabilityIncluded = true -------------------------------------------------------------------------------- /ballerina/Module.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | The Ballerina AWS SimpleDB provides the capability to manage domains in [AWS SimpleDB](https://aws.amazon.com/simpledb/). 3 | 4 | This module supports [Amazon SimpleDB REST API](https://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/Welcome.html) `2009-04-15` version. 5 | 6 | ## Prerequisites 7 | Before using this connector in your Ballerina application, complete the following: 8 | 1. Create an [AWS account](https://portal.aws.amazon.com/billing/signup?nc2=h_ct&src=default&redirect_url=https%3A%2F%2Faws.amazon.com%2Fregistration-confirmation#/start) 9 | 2. [Obtain tokens](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) 10 | 11 | ## Quickstart 12 | To use the AWS SimpleDB connector in your Ballerina application, update the .bal file as follows: 13 | 14 | ### Step 1: Import connector 15 | Import the `ballerinax/aws.simpledb` module into the Ballerina project. 16 | ```ballerina 17 | import ballerinax/aws.simpledb; 18 | ``` 19 | ### Step 2: Create a new connector instance 20 | 21 | You can now enter the credentials in the SimpleDB client configuration and create the SimpleDB client by passing the configuration as follows. 22 | 23 | ```ballerina 24 | simpledb:AwsCredentials awsCredentials = { 25 | accessKeyId: "", 26 | secretAccessKey: "" 27 | }; 28 | 29 | simpledb:ConnectionConfig config = { 30 | credentials:awsCredentials, 31 | region: 32 | }; 33 | 34 | simpledb:Client amazonSimpleDBClient = check new (config); 35 | ``` 36 | 37 | ### Step 3: Invoke connector operation 38 | 39 | 1. You can create a domain in Amazon SimpleDB as follows with `createDomain` method for a preferred domain name. 40 | 41 | ```ballerina 42 | simpledb:CreateDomainResponse response = check amazonSimpleDBClient->createDomain("NewTDomain"); 43 | log:printInfo("Created Domain: " + response.toString()); 44 | ``` 45 | 2. Use `bal run` command to compile and run the Ballerina program. 46 | 47 | **[You can find more samples here](https://github.com/ballerina-platform/module-ballerinax-aws.simpledb/tree/master/simpledb/samples)** 48 | -------------------------------------------------------------------------------- /ballerina/Package.md: -------------------------------------------------------------------------------- 1 | Connects to AWS simpledb from Ballerina 2 | 3 | ## Package overview 4 | 5 | The `ballerinax/aws.simpledb` is a [Ballerina](https://ballerina.io/) connector for [AWS SimpleDB](https://aws.amazon.com/simpledb/). It enables Ballerina developers to perform AWS SimpleDB related operations programmatically through the module `ballerinax/aws.simpledb`. 6 | 7 | ### Compatibility 8 | | | Version | 9 | |---------------------|--------------------| 10 | | Ballerina Language | Swan Lake 2201.8.0 | 11 | | Amazon SimpleDB API | 2009-04-15 | 12 | 13 | ## Report issues 14 | To report bugs, request new features, start new discussions, view project boards, etc., go to the [Ballerina Extended Library repository](https://github.com/ballerina-platform/ballerina-extended-library) 15 | 16 | ## Useful links 17 | - Discuss code changes of the Ballerina project via [ballerina-dev@googlegroups.com](mailto:ballerina-dev@googlegroups.com). 18 | - Chat live with us via our [Discord server](https://discord.gg/ballerinalang). 19 | - Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag 20 | -------------------------------------------------------------------------------- /ballerina/client.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/http; 18 | import ballerinax/'client.config; 19 | 20 | # Ballerina Amazon SimpleDB API connector provides the capability to access Amazon SimpleDB Service. 21 | # This connector lets you to create and manage the SimpleDB domains. 22 | # 23 | # + amazonSimpleDBClient - Connector HTTP endpoint 24 | # + accessKeyId - Amazon API access key 25 | # + secretAccessKey - Amazon API secret key 26 | # + securityToken - Security token 27 | # + region - Amazon API Region 28 | # + amazonHost - Amazon host name 29 | @display {label: "Amazon SimpleDB", iconPath: "resources/aws.simpledb.svg"} 30 | public isolated client class Client { 31 | final string accessKeyId; 32 | final string secretAccessKey; 33 | final string? securityToken; 34 | final string region; 35 | final string amazonHost; 36 | final http:Client amazonSimpleDBClient; 37 | 38 | # Initializes the connector. 39 | # 40 | # + config - Configuration for the connector 41 | # + httpClientConfig - HTTP Configuration 42 | # + return - `http:Error` in case of failure to initialize or `null` if successfully initialized 43 | public isolated function init(ConnectionConfig config) returns error? { 44 | self.accessKeyId = config.awsCredentials.accessKeyId; 45 | self.secretAccessKey = config.awsCredentials.secretAccessKey; 46 | self.securityToken = (config?.awsCredentials?.securityToken is string) ? 47 | (config?.awsCredentials?.securityToken) : (); 48 | self.region = config.region; 49 | http:ClientConfiguration httpClientConfig = check config:constructHTTPClientConfig(config); 50 | self.amazonHost = AMAZON_AWS_HOST; 51 | string baseURL = HTTPS + self.amazonHost; 52 | check validateCredentials(self.accessKeyId, self.secretAccessKey); 53 | self.amazonSimpleDBClient = check new (baseURL, httpClientConfig); 54 | } 55 | 56 | # Create a domain. 57 | # 58 | # + domainName - Name of domain 59 | # + return - `CreateDomainResponse` on success else an `error` 60 | remote isolated function createDomain(string domainName) returns @tainted CreateDomainResponse|xml|error { 61 | map parameters = {}; 62 | parameters[ACTION] = check urlEncode(CREATE_DOMAIN); 63 | parameters[DOMAIN_NAME] = check urlEncode(domainName); 64 | xml response = check sendRequest(self.amazonSimpleDBClient, generateRequest(), 65 | check generateQueryParameters(parameters, self.accessKeyId, self.secretAccessKey)); 66 | CreateDomainResponse|xml createdDomainResponse = check xmlToCreatedDomain(response); 67 | return createdDomainResponse; 68 | } 69 | 70 | # Get information about the domain, including when the domain was created, the number of items and attributes, and the size of attribute names and values. 71 | # 72 | # + domainName - Name of domain 73 | # + return - `DomainMetaDataResponse` on success else an `error` 74 | remote isolated function getDomainMetaData(string domainName) returns @tainted DomainMetaDataResponse|xml|error { 75 | map parameters = {}; 76 | parameters[ACTION] = check urlEncode(DOMAIN_METADATA); 77 | parameters[DOMAIN_NAME] = check urlEncode(domainName); 78 | xml response = check sendRequest(self.amazonSimpleDBClient, generateRequest(), 79 | check generateQueryParameters(parameters, self.accessKeyId, self.secretAccessKey)); 80 | DomainMetaDataResponse|xml domainMetaDataResponse = check xmlToDomainMetaData(response); 81 | return domainMetaDataResponse; 82 | } 83 | 84 | # Select set of attributes that match the select expression. 85 | # 86 | # + selectExpression - Select expression to get attributes 87 | # + consistentRead - True if consistent reads are to be accepted 88 | # + return - `SelectResponse` on success else an `error` 89 | remote isolated function 'select(string selectExpression, boolean consistentRead) returns @tainted SelectResponse|xml|error { 90 | map parameters = {}; 91 | parameters[ACTION] = check urlEncode(SELECT); 92 | parameters[SELECT_EXPRESSION] = check urlEncode(selectExpression); 93 | parameters[CONSISTENT_READ] = check urlEncode(consistentRead.toString()); 94 | xml response = check sendRequest(self.amazonSimpleDBClient, generateRequest(), 95 | check generateQueryParameters(parameters, self.accessKeyId, self.secretAccessKey)); 96 | SelectResponse|xml selectResponse = check xmlToSelectResponse(response); 97 | return selectResponse; 98 | } 99 | 100 | # list available domains. 101 | # 102 | # + return - `ListDomainsResponse` on success else an `error` 103 | remote isolated function listDomains() returns @tainted ListDomainsResponse|xml|error { 104 | map parameters = {}; 105 | parameters[ACTION] = check urlEncode(LIST_DOMAIN); 106 | xml response = check sendRequest(self.amazonSimpleDBClient, generateRequest(), 107 | check generateQueryParameters(parameters, self.accessKeyId, self.secretAccessKey)); 108 | ListDomainsResponse|xml listDomainsResponse = check xmlToListsDomain(response); 109 | return listDomainsResponse; 110 | } 111 | 112 | # Delete a domain. 113 | # 114 | # + domainName - Name of domain 115 | # + return - `DeleteDomainResponse` on success else an `error` 116 | remote isolated function deleteDomain(string domainName) returns @tainted DeleteDomainResponse|xml|error { 117 | map parameters = {}; 118 | parameters[ACTION] = check urlEncode(DELETE_DOMAIN); 119 | parameters[DOMAIN_NAME] = domainName; 120 | xml response = check sendRequest(self.amazonSimpleDBClient, generateRequest(), 121 | check generateQueryParameters(parameters, self.accessKeyId, self.secretAccessKey)); 122 | DeleteDomainResponse|xml deletedDomainResponse = check xmlToDeletedDomain(response); 123 | return deletedDomainResponse; 124 | } 125 | 126 | # Get all of the attributes associated with the item. 127 | # 128 | # + domainName - Name of domain 129 | # + itemName - Name of item 130 | # + consistentRead - True if consistent reads are to be accepted 131 | # + return - `GetAttributesResponse` on success else an `error` 132 | remote isolated function getAttributes(string domainName, string itemName, boolean consistentRead) returns @tainted GetAttributesResponse|xml|error { 133 | map parameters = {}; 134 | parameters[ACTION] = check urlEncode(GET_ATTRIBUTES); 135 | parameters[DOMAIN_NAME] = check urlEncode(domainName); 136 | parameters[ITEM_NAME] = check urlEncode(itemName); 137 | parameters[CONSISTENT_READ] = check urlEncode(consistentRead.toString()); 138 | xml response = check sendRequest(self.amazonSimpleDBClient, generateRequest(), 139 | check generateQueryParameters(parameters, self.accessKeyId, self.secretAccessKey)); 140 | GetAttributesResponse|xml getAttributesResponse = check xmlToGetAttributesResponse(response); 141 | return getAttributesResponse; 142 | } 143 | 144 | # Creates or replaces attributes in an item. 145 | # 146 | # + domainName - Name of domain 147 | # + itemName - Name of item 148 | # + attributes - Attributes to create or replace values 149 | # + return - `PutAttributesResponse` on success else an `error` 150 | remote isolated function putAttributes(string domainName, string itemName, Attribute attributes) returns @tainted PutAttributesResponse|xml|error { 151 | map parameters = {}; 152 | parameters[ACTION] = check urlEncode(PUT_ATTRIBUTES); 153 | parameters[DOMAIN_NAME] = check urlEncode(domainName); 154 | parameters[ITEM_NAME] = check urlEncode(itemName); 155 | xml response = check sendRequest(self.amazonSimpleDBClient, generateRequest(), 156 | check generateQueryParameters(parameters, self.accessKeyId, self.secretAccessKey)); 157 | PutAttributesResponse|xml putAttributesResponse = check xmlToPutAttributesResponse(response); 158 | return putAttributesResponse; 159 | } 160 | 161 | # Delete attributes in an item. 162 | # 163 | # + domainName - Name of domain 164 | # + itemName - Name of item 165 | # + attributes - Attributes to create or replace values 166 | # + return - `DeleteAttributesResponse` on success else an `error` 167 | remote isolated function deleteAttributes(string domainName, string itemName, Attribute attributes) returns @tainted DeleteAttributesResponse|xml|error { 168 | map parameters = {}; 169 | parameters[ACTION] = check urlEncode(DELETE_ATTRIBUTES); 170 | parameters[DOMAIN_NAME] = check urlEncode(domainName); 171 | parameters[ITEM_NAME] = check urlEncode(itemName); 172 | xml response = check sendRequest(self.amazonSimpleDBClient, generateRequest(), 173 | check generateQueryParameters(parameters, self.accessKeyId, self.secretAccessKey)); 174 | DeleteAttributesResponse|xml deleteAttributesResponse = check xmlToDeleteAttributesResponse(response); 175 | return deleteAttributesResponse; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /ballerina/constants.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | const ISO8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; 18 | const HTTPS = "https://"; 19 | const ENDPOINT = "/"; 20 | const POST = "POST"; 21 | const SDB_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"; 22 | const CONTENT_TYPE = "Content-Type"; 23 | const SIGNATURE = "Signature"; 24 | const ATTRIBUTE = "Attribute"; 25 | const NAME = "Name"; 26 | const VALUE = "Value"; 27 | const UTF_8 = "UTF-8"; 28 | const string AMAZON_AWS_HOST = "sdb.amazonaws.com"; 29 | const string DEFAULT_REGION = "us-east-1"; 30 | const string DOMAIN_NAME = "DomainName"; 31 | const string ITEM_NAME = "ItemName"; 32 | const string ACTION = "Action"; 33 | const string DOMAIN_METADATA = "DomainMetadata"; 34 | const string GET_ATTRIBUTES = "GetAttributes"; 35 | const string PUT_ATTRIBUTES = "PutAttributes"; 36 | const string DELETE_ATTRIBUTES = "DeleteAttributes"; 37 | const string SELECT = "Select"; 38 | const string CREATE_DOMAIN = "CreateDomain"; 39 | const string LIST_DOMAIN = "ListDomains"; 40 | const string DELETE_DOMAIN = "DeleteDomain"; 41 | const string VERSION = "Version"; 42 | const string ACCESS_KEY = "AWSAccessKeyId"; 43 | const string SIGNATURE_VERSION = "SignatureVersion"; 44 | const string TIME_STAMP = "Timestamp"; 45 | const string SIGNATURE_METHOD = "SignatureMethod"; 46 | const string HMAC_SHA_256 = "HmacSHA256"; 47 | const string CONSISTENT_READ = "ConsistentRead"; 48 | const string SELECT_EXPRESSION = "SelectExpression"; 49 | const string VERSION_NUMBER = "2009-04-15"; 50 | const string OPERATION_ERROR = "Error has occurred during an operation"; 51 | const string REQUEST_ERROR = "Error has occurred during request"; 52 | const string STATUS_CODE = "status code"; 53 | const string FULL_STOP = "."; 54 | const string AMBERSAND = "&"; 55 | const string EQUAL = "="; 56 | const string NEW_LINE = "\n"; 57 | const string ERROR = "error"; 58 | const string EMPTY_STRING = ""; 59 | const string Z = "Z"; 60 | const string TWO = "2"; 61 | const string EMPTY_CREDENTIALS = "Access Key Id or Secret Access Key credential are empty"; 62 | -------------------------------------------------------------------------------- /ballerina/data_mappings.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | xmlns "http://sdb.amazonaws.com/doc/2009-04-15/" as namespace; 18 | 19 | isolated function xmlToCreatedDomain(xml response) returns CreateDomainResponse|xml|error { 20 | xml responseMeta = response/; 21 | if (responseMeta.toString() != EMPTY_STRING) { 22 | ResponseMetadata responseMetadata = { 23 | requestId: (responseMeta//*).toString(), 24 | boxUsage: (responseMeta//*).toString() 25 | }; 26 | CreateDomainResponse createDomainResponse = { 27 | responseMetadata: responseMetadata 28 | }; 29 | return createDomainResponse; 30 | } else { 31 | return response; 32 | } 33 | } 34 | 35 | isolated function xmlToDomainMetaData(xml response) returns DomainMetaDataResponse|xml|error { 36 | xml domainMetadata = response/; 37 | xml responseMeta = response/; 38 | if (responseMeta.toString() != EMPTY_STRING) { 39 | DomainMetadataResult domainMetadataResult = { 40 | itemCount: (domainMetadata//*).toString(), 41 | itemNamesSizeBytes: (domainMetadata//*).toString(), 42 | attributeNameCount: (domainMetadata//*).toString(), 43 | attributeNamesSizeBytes: (domainMetadata//*).toString(), 44 | attributeValueCount: (domainMetadata//*).toString(), 45 | attributeValuesSizeBytes: (domainMetadata//*).toString(), 46 | timestamp: (domainMetadata//*).toString() 47 | }; 48 | ResponseMetadata responseMetadata = { 49 | requestId: (responseMeta//*).toString(), 50 | boxUsage: (responseMeta//*).toString() 51 | }; 52 | DomainMetaDataResponse domainMetaDataResponse = { 53 | domainMetadataResult: domainMetadataResult, 54 | responseMetadata: responseMetadata 55 | }; 56 | return domainMetaDataResponse; 57 | } else { 58 | return response; 59 | } 60 | } 61 | 62 | isolated function xmlToSelectResponse(xml response) returns SelectResponse|xml|error { 63 | xml selectdResult = response/; 64 | xml responseMeta = response/; 65 | if (responseMeta.toString() != EMPTY_STRING) { 66 | SelectResult selectResult = { 67 | items: (selectdResult//*).toString() 68 | }; 69 | ResponseMetadata responseMetadata = { 70 | requestId: (responseMeta//*).toString(), 71 | boxUsage: (responseMeta//*).toString() 72 | }; 73 | SelectResponse selectResponse = { 74 | selectResult: selectResult, 75 | responseMetadata: responseMetadata 76 | }; 77 | return selectResponse; 78 | } else { 79 | return response; 80 | } 81 | } 82 | 83 | isolated function xmlToGetAttributesResponse(xml response) returns GetAttributesResponse|xml|error { 84 | xml getAttributeResult = response/; 85 | xml responseMeta = response/; 86 | if (responseMeta.toString() != EMPTY_STRING) { 87 | GetAttributesResult getAttributesResult = { 88 | attributes: (getAttributeResult//*).toString() 89 | }; 90 | ResponseMetadata responseMetadata = { 91 | requestId: (responseMeta//*).toString(), 92 | boxUsage: (responseMeta//*).toString() 93 | }; 94 | GetAttributesResponse getAttributesResponse = { 95 | getAttributesResult: getAttributesResult, 96 | responseMetadata: responseMetadata 97 | }; 98 | return getAttributesResponse; 99 | } else { 100 | return response; 101 | } 102 | } 103 | 104 | isolated function xmlToPutAttributesResponse(xml response) returns PutAttributesResponse|xml|error { 105 | xml responseMeta = response/; 106 | if (responseMeta.toString() != EMPTY_STRING) { 107 | ResponseMetadata responseMetadata = { 108 | requestId: (responseMeta//*).toString(), 109 | boxUsage: (responseMeta//*).toString() 110 | }; 111 | PutAttributesResponse putAttributesResponse = { 112 | responseMetadata: responseMetadata 113 | }; 114 | return putAttributesResponse; 115 | } else { 116 | return response; 117 | } 118 | } 119 | 120 | isolated function xmlToDeleteAttributesResponse(xml response) returns DeleteAttributesResponse|xml|error { 121 | xml responseMeta = response/; 122 | if (responseMeta.toString() != EMPTY_STRING) { 123 | ResponseMetadata responseMetadata = { 124 | requestId: (responseMeta//*).toString(), 125 | boxUsage: (responseMeta//*).toString() 126 | }; 127 | DeleteAttributesResponse deleteAttributesResponse = { 128 | responseMetadata: responseMetadata 129 | }; 130 | return deleteAttributesResponse; 131 | } else { 132 | return response; 133 | } 134 | } 135 | 136 | isolated function xmlToDeletedDomain(xml response) returns DeleteDomainResponse|xml|error { 137 | xml responseMeta = response/; 138 | if (responseMeta.toString() != EMPTY_STRING) { 139 | ResponseMetadata responseMetadata = { 140 | requestId: (responseMeta//*).toString(), 141 | boxUsage: (responseMeta//*).toString() 142 | }; 143 | DeleteDomainResponse deleteDomainResponse = { 144 | responseMetadata: responseMetadata 145 | }; 146 | return deleteDomainResponse; 147 | } else { 148 | return response; 149 | } 150 | } 151 | 152 | isolated function xmlToListsDomain(xml response) returns ListDomainsResponse|xml|error { 153 | xml listDomainResult = response/; 154 | xml responseMeta = response/; 155 | if (responseMeta.toString() != EMPTY_STRING) { 156 | ListDomainsResult listDomainsResult = { 157 | domainNames: (listDomainResult//*).toString(), 158 | nextToken: (listDomainResult//*).toString() 159 | }; 160 | ResponseMetadata responseMetadata = { 161 | requestId: (responseMeta//*).toString(), 162 | boxUsage: (responseMeta//*).toString() 163 | }; 164 | ListDomainsResponse listDomainsResponse = { 165 | listDomainsResult: listDomainsResult, 166 | responseMetadata: responseMetadata 167 | }; 168 | return listDomainsResponse; 169 | } else { 170 | return response; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /ballerina/error.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | public type GenerateRequestFailed distinct error; 18 | 19 | public type OperationError distinct error; 20 | 21 | public type DataMappingError distinct error; 22 | 23 | public type FileReadFailed distinct error; 24 | 25 | public type ResponseHandleFailed distinct error; 26 | 27 | const string CONVERT_XML_TO_INBOUND_MESSAGES_FAILED_MSG = "Error while converting XML to Inbound Messages."; 28 | const string CONVERT_XML_TO_INBOUND_MESSAGE_FAILED_MSG = "Error while converting XML to an Inbound Message."; 29 | const string CONVERT_XML_TO_INBOUND_MESSAGE_MESSAGE_ATTRIBUTES_FAILED_MSG = "Error while converting XML to an Inbound Message's Message Attributes."; 30 | const string CONVERT_XML_TO_INBOUND_MESSAGE_MESSAGE_ATTRIBUTE_FAILED_MSG = "Error while converting XML to an Inbound Message's Message Attribute."; 31 | const string FILE_READ_FAILED_MSG = "Error while reading a file."; 32 | const string CLOSE_CHARACTER_STREAM_FAILED_MSG = "Error occurred while closing character stream."; 33 | const string GENERATE_REQUEST_FAILED_MSG = "Error occurred while generating POST request."; 34 | const string NO_CONTENT_SET_WITH_RESPONSE_MSG = "No Content was sent with the response."; 35 | const string RESPONSE_PAYLOAD_IS_NOT_XML_MSG = "Response payload is not XML."; 36 | const string ERROR_OCCURRED_WHILE_INVOKING_REST_API_MSG = "Error occurred while invoking the REST API."; 37 | const string OUTBOUND_MESSAGE_RESPONSE_EMPTY_MSG = "Outbound Message response is empty."; 38 | const string OPERATION_ERROR_MSG = "Error has occurred during an operation."; 39 | const string UNREACHABLE_STATE = "Response type cannot be http payload"; 40 | -------------------------------------------------------------------------------- /ballerina/external.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/jballerina.java; 18 | 19 | isolated function ofEpochSecond(int epochSeconds, int nanoAdjustments) returns handle = @java:Method { 20 | 'class: "java.time.Instant", 21 | name: "ofEpochSecond" 22 | } external; 23 | 24 | isolated function getZoneId(handle zoneId) returns handle = @java:Method { 25 | 'class: "java.time.ZoneId", 26 | name: "of" 27 | } external; 28 | 29 | isolated function atZone(handle receiver, handle zoneId) returns handle = @java:Method { 30 | 'class: "java.time.Instant", 31 | name: "atZone" 32 | } external; 33 | 34 | isolated function ofPattern(handle pattern) returns handle = @java:Method { 35 | 'class: "java.time.format.DateTimeFormatter", 36 | name: "ofPattern" 37 | } external; 38 | 39 | isolated function format(handle receiver, handle dateTimeFormatter) returns handle = @java:Method { 40 | 'class: "java.time.ZonedDateTime", 41 | name: "format" 42 | } external; 43 | -------------------------------------------------------------------------------- /ballerina/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-aws.simpledb/0a86ffc42eaa170aa5f82dde4ed3429e5a6ffd51/ballerina/icon.png -------------------------------------------------------------------------------- /ballerina/tests/test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/test; 18 | import ballerina/os; 19 | 20 | configurable string accessKeyId = os:getEnv("ACCESS_KEY_ID"); 21 | configurable string secretAccessKey = os:getEnv("SECRET_ACCESS_KEY"); 22 | configurable string region = os:getEnv("REGION"); 23 | 24 | AwsCredentials awsCredentials = { 25 | accessKeyId: accessKeyId, 26 | secretAccessKey: secretAccessKey 27 | }; 28 | 29 | ConnectionConfig config = { 30 | awsCredentials: awsCredentials 31 | }; 32 | 33 | Client amazonSimpleDBClient = check new (config); 34 | 35 | @test:Config {} 36 | function testCreateDomain() returns error? { 37 | CreateDomainResponse|xml response = check amazonSimpleDBClient->createDomain("test"); 38 | assertForResponseErrors(response); 39 | } 40 | 41 | @test:Config {dependsOn: [testCreateDomain]} 42 | function testListDomains() returns error? { 43 | ListDomainsResponse|xml response = check amazonSimpleDBClient->listDomains(); 44 | assertForResponseErrors(response); 45 | } 46 | 47 | @test:Config {dependsOn: [testListDomains]} 48 | function testGetDomainMetaData() returns error? { 49 | DomainMetaDataResponse|xml response = check amazonSimpleDBClient->getDomainMetaData("test"); 50 | assertForResponseErrors(response); 51 | } 52 | 53 | @test:Config {dependsOn: [testGetDomainMetaData]} 54 | function testSelect() returns error? { 55 | string selectExpression = "select output_list from test"; 56 | SelectResponse|xml response = check amazonSimpleDBClient->'select(selectExpression, true); 57 | assertForResponseErrors(response); 58 | } 59 | 60 | @test:Config {dependsOn: [testGetAttributes]} 61 | function testDeleteDomain() returns error? { 62 | DeleteDomainResponse|xml response = check amazonSimpleDBClient->deleteDomain("test"); 63 | assertForResponseErrors(response); 64 | } 65 | 66 | @test:Config {dependsOn: [testSelect]} 67 | function testGetAttributes() returns error? { 68 | GetAttributesResponse|xml response = check amazonSimpleDBClient->getAttributes("test", "output_list", true); 69 | assertForResponseErrors(response); 70 | } 71 | 72 | function assertForResponseErrors(anydata response) { 73 | if response is xml { 74 | test:assertFalse((response//*).data() != "", msg = response.toBalString()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ballerina/types.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerinax/'client.config; 18 | 19 | # Represents the AWS SimpleDB Connector configurations. 20 | @display {label: "Connection Config"} 21 | public type ConnectionConfig record {| 22 | *config:ConnectionConfig; 23 | never auth?; 24 | # AWS credentials 25 | AwsCredentials|AwsTemporaryCredentials awsCredentials; 26 | # AWS Region 27 | string region = DEFAULT_REGION; 28 | |}; 29 | 30 | # Represents AWS credentials. 31 | # 32 | # + accessKeyId - AWS access key 33 | # + secretAccessKey - AWS secret key 34 | public type AwsCredentials record { 35 | string accessKeyId; 36 | @display { 37 | label: "", 38 | kind: "password" 39 | } 40 | string secretAccessKey; 41 | }; 42 | 43 | # Represents AWS temporary credentials. 44 | # 45 | # + accessKeyId - AWS access key 46 | # + secretAccessKey - AWS secret key 47 | # + securityToken - AWS secret token 48 | public type AwsTemporaryCredentials record { 49 | string accessKeyId; 50 | @display { 51 | label: "", 52 | kind: "password" 53 | } 54 | string secretAccessKey; 55 | @display { 56 | label: "", 57 | kind: "password" 58 | } 59 | string securityToken; 60 | }; 61 | 62 | # An attribute for the item 63 | # 64 | # + name - Name of the attribute 65 | # + value - Value of the attribute 66 | public type Attribute record { 67 | string name; 68 | string value; 69 | }; 70 | 71 | # Create domain response 72 | # 73 | # + responseMetadata - Response metadata 74 | public type CreateDomainResponse record { 75 | ResponseMetadata responseMetadata; 76 | }; 77 | 78 | # Delete domain response 79 | # 80 | # + responseMetadata - Response metadata 81 | public type DeleteDomainResponse record { 82 | ResponseMetadata responseMetadata; 83 | }; 84 | 85 | # List domains response 86 | # 87 | # + listDomainsResult - Result of domain list 88 | # + responseMetadata - Response metadata 89 | public type ListDomainsResponse record { 90 | ListDomainsResult listDomainsResult; 91 | ResponseMetadata responseMetadata; 92 | }; 93 | 94 | # Result of domain list 95 | # 96 | # + domainNames - Domain names 97 | # + nextToken - Next token of returned list items more than page size 98 | public type ListDomainsResult record { 99 | string domainNames; 100 | string nextToken; 101 | }; 102 | 103 | # Select response 104 | # 105 | # + selectResult - Result of selectn 106 | # + responseMetadata - Response metadata 107 | public type SelectResponse record { 108 | SelectResult selectResult; 109 | ResponseMetadata responseMetadata; 110 | }; 111 | 112 | # Result of select 113 | # 114 | # + items - items available 115 | public type SelectResult record { 116 | string items; 117 | }; 118 | 119 | # Get attributes response 120 | # 121 | # + getAttributesResult - Result of get attributes 122 | # + responseMetadata - Response metadata 123 | public type GetAttributesResponse record { 124 | GetAttributesResult getAttributesResult; 125 | ResponseMetadata responseMetadata; 126 | }; 127 | 128 | # Result of get attributes 129 | # 130 | # + attributes - Attribute names 131 | public type GetAttributesResult record { 132 | string attributes; 133 | }; 134 | 135 | # Put attributes response 136 | # 137 | # + responseMetadata - Response metadata 138 | public type PutAttributesResponse record { 139 | ResponseMetadata responseMetadata; 140 | }; 141 | 142 | # Delete attributes response 143 | # 144 | # + responseMetadata - Response metadata 145 | public type DeleteAttributesResponse record { 146 | ResponseMetadata responseMetadata; 147 | }; 148 | 149 | # Domain metadata response 150 | # 151 | # + domainMetadataResult - Result of domain metadata 152 | # + responseMetadata - Response metadata 153 | public type DomainMetaDataResponse record { 154 | DomainMetadataResult domainMetadataResult; 155 | ResponseMetadata responseMetadata; 156 | }; 157 | 158 | # Domain metadata result 159 | # 160 | # + itemCount - Number of items available 161 | # + itemNamesSizeBytes - Items names size in bytes 162 | # + attributeNameCount - Number of attributes available 163 | # + attributeNamesSizeBytes - Attributes names size in bytes 164 | # + attributeValueCount - Value of attributes available 165 | # + attributeValuesSizeBytes - Attributes values size in bytes 166 | # + timestamp - Timestamp 167 | public type DomainMetadataResult record { 168 | string itemCount; 169 | string itemNamesSizeBytes; 170 | string attributeNameCount; 171 | string attributeNamesSizeBytes; 172 | string attributeValueCount; 173 | string attributeValuesSizeBytes; 174 | string timestamp; 175 | }; 176 | 177 | # Denote response metadata 178 | # 179 | # + requestId - A unique ID for tracking the request 180 | # + boxUsage - The measure of machine utilization for this request 181 | public type ResponseMetadata record { 182 | string requestId; 183 | string boxUsage; 184 | }; 185 | -------------------------------------------------------------------------------- /ballerina/utils.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/crypto; 18 | import ballerina/jballerina.java; 19 | import ballerina/lang.array; 20 | import ballerina/http; 21 | import ballerina/time; 22 | import ballerina/url; 23 | 24 | isolated function generateQueryParameters(map parameters, string accessKeyId, string secretAccessKey) returns string|error { 25 | map sortedParameters = check updateAndSortParameters(parameters, accessKeyId); 26 | string formattedParameters = check calculateStringToSignV2(sortedParameters); 27 | string signatureString = check sign(formattedParameters, secretAccessKey); 28 | sortedParameters[SIGNATURE] = check urlEncode(signatureString); 29 | return buildPayload(sortedParameters); 30 | } 31 | 32 | isolated function generateTimestamp() returns string|error { 33 | [int, decimal] & readonly currentTime = time:utcNow(); 34 | string amzDate = check utcToString(currentTime, ISO8601_FORMAT); 35 | return amzDate; 36 | } 37 | 38 | isolated function generateRequest() returns http:Request { 39 | http:Request request = new; 40 | request.setHeader(CONTENT_TYPE, SDB_CONTENT_TYPE); 41 | return request; 42 | } 43 | 44 | isolated function sendRequest(http:Client amazonSimpleDBClient, http:Request|error request, string query) returns @tainted xml|error { 45 | if request is http:Request { 46 | http:Response|error httpResponse = amazonSimpleDBClient->post("/?" + query, request); 47 | return handleResponse(httpResponse); 48 | } else { 49 | return error(REQUEST_ERROR); 50 | } 51 | } 52 | 53 | isolated function validateCredentials(string accessKeyId, string secretAccessKey) returns error? { 54 | if accessKeyId == EMPTY_STRING || secretAccessKey == EMPTY_STRING { 55 | return error(EMPTY_CREDENTIALS); 56 | } 57 | return; 58 | } 59 | 60 | isolated function utcToString(time:Utc utc, string pattern) returns string|error { 61 | [int, decimal] [epochSeconds, lastSecondFraction] = utc; 62 | int nanoAdjustments = (lastSecondFraction * 1000000000); 63 | var instant = ofEpochSecond(epochSeconds, nanoAdjustments); 64 | var zoneId = getZoneId(java:fromString(Z)); 65 | var zonedDateTime = atZone(instant, zoneId); 66 | var dateTimeFormatter = ofPattern(java:fromString(pattern)); 67 | handle formatString = format(zonedDateTime, dateTimeFormatter); 68 | return formatString.toBalString(); 69 | } 70 | 71 | # Set attributes to a map of string to add as query parameters. 72 | # 73 | # + parameters - Parameter map 74 | # + attributes - Attribute to convert to a map of string 75 | # + return - If successful returns `map` response. Else returns error 76 | isolated function setAttributes(map parameters, Attribute attributes) returns map { 77 | int attributeNumber = 1; 78 | map attributeMap = >attributes; 79 | foreach var [key, value] in attributeMap.entries() { 80 | string attributeName = getAttributeName(key); 81 | parameters[ATTRIBUTE + FULL_STOP + attributeNumber.toString() + FULL_STOP + NAME] = attributeName.toString(); 82 | parameters[ATTRIBUTE + FULL_STOP + attributeNumber.toString() + FULL_STOP + VALUE] = value.toString(); 83 | attributeNumber = attributeNumber + 1; 84 | } 85 | return parameters; 86 | } 87 | 88 | # Handles the HTTP response. 89 | # 90 | # + httpResponse - Http response or error 91 | # + return - If successful returns `xml` response. Else returns error 92 | isolated function handleResponse(http:Response|error httpResponse) returns @untainted xml|error { 93 | if httpResponse is http:Response { 94 | if httpResponse.statusCode == http:STATUS_NO_CONTENT { 95 | return error ResponseHandleFailed(NO_CONTENT_SET_WITH_RESPONSE_MSG); 96 | } 97 | var xmlResponse = httpResponse.getXmlPayload(); 98 | return xmlResponse; 99 | } else { 100 | return error(ERROR_OCCURRED_WHILE_INVOKING_REST_API_MSG, httpResponse); 101 | } 102 | } 103 | 104 | isolated function getAttributeName(string attribute) returns string { 105 | string firstLetter = attribute.substring(0, 1); 106 | string otherLetters = attribute.substring(1); 107 | string upperCaseFirstLetter = firstLetter.toUpperAscii(); 108 | string attributeName = upperCaseFirstLetter + otherLetters; 109 | return attributeName; 110 | } 111 | 112 | isolated function urlEncode(string rawValue) returns string|error { 113 | string encoded = check url:encode(rawValue, UTF_8); 114 | encoded = re `\+`.replaceAll(encoded, "%20"); 115 | encoded = re `\*`.replaceAll(encoded, "%2A"); 116 | encoded = re `%7E`.replaceAll(encoded, "~"); 117 | return encoded; 118 | } 119 | 120 | isolated function updateAndSortParameters(map parameters, string accessKeyId) returns map|error { 121 | parameters[ACCESS_KEY] = check urlEncode(accessKeyId); 122 | parameters[SIGNATURE_VERSION] = check urlEncode(TWO); 123 | parameters[TIME_STAMP] = check urlEncode(check generateTimestamp()); 124 | parameters[SIGNATURE_METHOD] = check urlEncode(HMAC_SHA_256); 125 | parameters[VERSION] = check urlEncode(VERSION_NUMBER); 126 | return sortParameters(parameters); 127 | } 128 | 129 | isolated function calculateStringToSignV2(map parameters) returns string|error { 130 | map sortedParameters = sortParameters(parameters); 131 | string stringToSign = EMPTY_STRING; 132 | stringToSign += POST + NEW_LINE; 133 | stringToSign += AMAZON_AWS_HOST + NEW_LINE; 134 | stringToSign += ENDPOINT + NEW_LINE; 135 | stringToSign += buildPayload(sortedParameters); 136 | return stringToSign; 137 | } 138 | 139 | isolated function buildPayload(map parameters) returns string { 140 | string payload = EMPTY_STRING; 141 | int parameterNumber = 1; 142 | foreach var [key, value] in parameters.entries() { 143 | if parameterNumber > 1 { 144 | payload += AMBERSAND; 145 | } 146 | payload += key + EQUAL + value; 147 | parameterNumber += 1; 148 | } 149 | return payload; 150 | } 151 | 152 | isolated function sortParameters(map parameters) returns map { 153 | string[] keys = parameters.keys(); 154 | keys = keys.sort(); 155 | map sortedParameters = {}; 156 | foreach var key in keys { 157 | string? value = parameters[key]; 158 | sortedParameters[key] = value is string ? value : EMPTY_STRING; 159 | } 160 | return sortedParameters; 161 | } 162 | 163 | isolated function sign(string data, string secretKey) returns string|error { 164 | return array:toBase64(check crypto:hmacSha256(data.toBytes(), secretKey.toBytes())); 165 | } 166 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | **Description:** 2 | 3 | 4 | **Suggested Labels:** 5 | 6 | 7 | **Suggested Assignees:** 8 | 9 | 10 | **Affected Product Version:** 11 | 12 | **OS, DB, other environment details and versions:** 13 | 14 | **Steps to reproduce:** 15 | 16 | 17 | **Related Issues:** 18 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | > Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc. 3 | 4 | ## Goals 5 | > Describe the solutions that this feature/fix will introduce to resolve the problems described above 6 | 7 | ## Approach 8 | > Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here. 9 | 10 | ## User stories 11 | > Summary of user stories addressed by this change> 12 | 13 | ## Release note 14 | > Brief description of the new feature or bug fix as it will appear in the release notes 15 | 16 | ## Documentation 17 | > Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact 18 | 19 | ## Training 20 | > Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable 21 | 22 | ## Certification 23 | > Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why. 24 | 25 | ## Marketing 26 | > Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable 27 | 28 | ## Automation tests 29 | - Unit tests 30 | > Code coverage information 31 | - Integration tests 32 | > Details about the test cases and coverage 33 | 34 | ## Security checks 35 | - Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? yes/no 36 | - Ran FindSecurityBugs plugin and verified report? yes/no 37 | - Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes/no 38 | 39 | ## Samples 40 | > Provide high-level details about the samples related to this feature 41 | 42 | ## Related PRs 43 | > List any other related PRs 44 | 45 | ## Migrations (if applicable) 46 | > Describe migration steps and platforms on which migration has been tested 47 | 48 | ## Test environment 49 | > List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested 50 | 51 | ## Learning 52 | > Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem. --------------------------------------------------------------------------------